← На главную

Пытаюсь изучать Objective Caml

Все-таки есть основания полагать, что Haskell местами излишне сложен и несколько оторван от действительности. Когда речь заходит о реальной разработке, возникает желание писать на языке попроще, где по умолчанию не используются ленивые вычисления, где при необходимости можно прибегнуть к ссылкам, наследованию, побочным эффектам и тп. И чтобы никаких матанов типа монад, аппликативных функторов, iteratees и застежек. Примерно как в OCaml.

Некоторые сведения об OCaml:

  • Язык был разработан во французском институте INRIA в 1985 году и развивается по сей день;
  • Это мультипарадигменный (функциональный, императивный, объектно-ориентированный) язык программирования общего назначения c автоматической сборкой мусора;
  • В OCaml используется cтрогая статическая типизация, предусмотрен автоматический вывод типов;
  • Язык поддерживает лямбды, замыкания, каррирование, хвостовую рекурсию, паттерн матчинг и неизменяемые данные (таковыми они являются обычно);
  • Также в OCaml есть указатели, циклы, изменяемые данные и функции с побочными эффектами;
  • По умолчанию вычисление является строгим, но при необходимости можно использовать и ленивые вычисления или просто передавать функции в качестве аргументов;
  • Программа на OCaml может интерпретироваться, компилироваться в байткод виртуальной машины (под названием Zinc) или в машинный код;
  • Еще на OCaml можно писать под JVM, а также под .NET, где язык почему-то называется F# (шутка, но на самом деле есть и настоящий OCaml под .NET);
  • Отступы в OCaml можно ставить как угодно;
  • В плане производительности OCaml сравним с Java, Haskell и Go;

Установка OCaml в Linux:

sudo aptitude install ocaml-batteries-included ocaml-doc

Также вам может захотеться установить менеджер пакетов opam. Готового deb-пакета на момент написания этих строк еще не было (это относительно молодой проект). Однако можно установить opam следующим образом:

wget https://raw.github.com/OCamlPro/opam/master/shell/install.sh \ && sh ./install.sh

После установки говорим opam init и следуем инструкциям. Затем читаем opam help.

По аналогичной схеме и почти без танцев с бубнами OCaml устанавливается под FreeBSD. Также на официальном сайте языка доступны бинарные пакеты для Windows и MacOS.

В качестве своей первой программы на OCaml я решил написать генератор лабиринтов (ага, еще один). Программа получилась довольно большой, поэтому здесь я приведу только некоторые ее части для демонстрации особенностей синтаксиса OCaml. Посмотреть исходный код полностью вы можете в этом архиве.

(* Ячейка лабиринта *) type cell = { mutable move_up : bool; mutable move_left : bool; } (* Лабиринт --- это матрица ячеек *) type maze = cell array array

Объявление типов в OCaml выглядит почти так же, как в Haskell. Ключевое слово mutable означает, что поле записи является изменяемым. Однострочных комментариев не предусмотрено, зато корректно обрабатываются вложенные комментарии.

(* Вывод заданной строки лабиринта *) let print_maze_row m y = let width = maze_width m in let top_str = String.create (width*2) and middle_str = String.create (width*2) in for x = 0 to width-1 do let c = m.(x).(y) in let top_char = if c.move_up then ' ' else '-' and left_char = if c.move_left then ' ' else '|' in String.set top_str (x*2) '+'; String.set top_str (x*2 + 1) top_char; String.set middle_str (x*2) left_char; String.set middle_str (x*2 + 1) ' ' done; print_string top_str; print_endline "+"; print_string middle_str; print_endline "|" (* Вывод лабиринта *) let print_maze m = for y = 0 to maze_max_y m do print_maze_row m y done; for x = 0 to maze_max_x m do print_string "+-" done; print_endline "+"

В отличие от Haskell в OCaml строки – это не списки символов. К тому же, строки в OCaml являются изменяемыми. Функции в OCaml могут иметь побочные эффекты, наличие или отсутствие которых не отражается в типе функции. Обратите также внимание на использование циклов.

(* Генерируем список с координатами всех ячеек в лабиринте заданного размера *) let not_visited_cells_list width height = let lst = ref [] in for y = height-1 downto 0 do for x = width-1 downto 0 do lst := (x,y) :: !lst done done; !lst

Здесь lst – это указатель на список. Во вложенном цикле создается новая cons-ячейка с указателем на голову списка (восклицательный знак означает разыменование ссылки), а в lst записывается указатель на эту cons-ячейку. В интернетах пишут, что OCaml поддерживает генераторы списков, но, чтобы их получить, нужно произвести какие-то дополнительные телодвижения. В этом вопросе я пока не разобрался.

let empty_maze width height = if width <= 0 then raise (Invalid_argument "width") else if height <= 0 then raise (Invalid_argument "height") else (* ... *)

Так в OCaml бросаются исключения. А так они ловятся:

try Hashtbl.find visited (x,y) with Not_found -> false

Чтобы запустить программу, достаточно сказать:

ocaml maze.ml

Однако этим вы запустите интерпретатор, который по совместительству является и REPL-средой (которую для удобства я лично оборачиваю в rlwrap). Для компиляции программы нужно выполнить команду:

ocaml maze.ml -o maze

Но если вы посмотрите на полученный бинарник через hexdump, до заметите в нем подозрительную строчку:

#!/usr/bin/ocamlrun

… которая как бы намекает, что что-то здесь не так. И действительно, на самом деле мы скомпилировали программу в байткод виртуальной машины Zinc. Эта программа будет работать на любой машине, но только если на ней есть ocamlrun.

Чтобы получить нормальный бинарник, требуется выполнить команду:

ocamlopt maze.ml -o maze

В этом случае программа будет работать быстрее и не будет зависеть от наличия виртуальной машины, но за это придется заплатить переносимостью. Еще, как вариант, можно прицепить виртуальную машину к байткоду, сказав:

ocamlc -custom maze.ml -o maze

Но в этом случае мы не получаем ни скорости, ни переносимости. Не совсем понятно, зачем вообще нужна такая возможность.

Пример сгенерированного лабиринта:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | | | | + +-+ +-+ + + +-+ +-+ + +-+-+ + +-+-+ + + + + + + +-+ +-+ + +-+-+ | | | | | | | | | | | | | | | | | | | + +-+-+ + + +-+ +-+ + +-+ +-+ +-+ +-+ +-+-+-+ +-+ +-+ +-+-+-+ + + | | | | | | | | | | | | | | + +-+ +-+ + + +-+ +-+ +-+ +-+-+ + +-+-+ +-+ +-+-+ +-+-+ + +-+ + + | | | | | | | | | | | | | | | | | | | | | + + + +-+ +-+-+ +-+ + +-+ +-+ +-+ +-+-+ +-+ + + + + + +-+ +-+-+ + | | | | | | | | | | | | | | | | | | + +-+-+ + + + +-+ +-+ + +-+-+ + +-+ + + + +-+-+-+ + +-+ +-+ +-+ + | | | | | | | | | | | | | | | | | | | | | + +-+ + +-+-+-+-+ + +-+ +-+-+ +-+ + + + +-+ +-+ +-+ +-+ + + +-+-+ | | | | | | | | | | | | | | | | | | | | | | | + + +-+ + +-+-+ +-+ + +-+-+ + + +-+-+ +-+-+ + + + + + + +-+-+ +-+ | | | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

В целом, язык мне очень понравился. Пусть OCaml по сравнению с Haskell выглядит несколько неказисто, зато мне он кажется куда более прагматичным. Несмотря на свою элегантность, в некоторых вопросах Haskell больно уж академичен. Например, если я хочу изменяемое состояние, Haskell предлагает мне подпорки в виде State и StateT. Нужен контролируемый ввод-вывод? Нет проблем, у нас есть другая подпорка – iteratees. Черт, я просто хочу читать из сокета и иметь изменяемое состояние!

Ставшая уже традиционной подборка ссылок:

А вы что думаете об OCaml?

Дополнение: Также совсем недавно вышла книга Real World OCaml, которая, по всей видимости, намного актуальнее всех названных выше книг.

Дополнение: См также мою заметку про OCaml Batteries Included.