Пытаюсь изучать Objective Caml
6 февраля 2013
Все-таки есть основания полагать, что 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:
Также вам может захотеться установить менеджер пакетов opam. Готового deb-пакета на момент написания этих строк еще не было (это относительно молодой проект). Однако можно установить opam следующим образом:
&& 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 поддерживает генераторы списков, но, чтобы их получить, нужно произвести какие-то дополнительные телодвижения. В этом вопросе я пока не разобрался.
if width <= 0 then raise (Invalid_argument "width")
else if height <= 0 then raise (Invalid_argument "height")
else (* ... *)
Так в OCaml бросаются исключения. А так они ловятся:
Hashtbl.find visited (x,y)
with
Not_found -> false
Чтобы запустить программу, достаточно сказать:
Однако этим вы запустите интерпретатор, который по совместительству является и REPL-средой (которую для удобства я лично оборачиваю в rlwrap). Для компиляции программы нужно выполнить команду:
Но если вы посмотрите на полученный бинарник через hexdump, до заметите в нем подозрительную строчку:
… которая как бы намекает, что что-то здесь не так. И действительно, на самом деле мы скомпилировали программу в байткод виртуальной машины Zinc. Эта программа будет работать на любой машине, но только если на ней есть ocamlrun.
Чтобы получить нормальный бинарник, требуется выполнить команду:
В этом случае программа будет работать быстрее и не будет зависеть от наличия виртуальной машины, но за это придется заплатить переносимостью. Еще, как вариант, можно прицепить виртуальную машину к байткоду, сказав:
Но в этом случае мы не получаем ни скорости, ни переносимости. Не совсем понятно, зачем вообще нужна такая возможность.
Пример сгенерированного лабиринта:
| | | | | | | |
+ +-+ +-+ + + +-+ +-+ + +-+-+ + +-+-+ + + + + + + +-+ +-+ + +-+-+
| | | | | | | | | | | | | | | | | | |
+ +-+-+ + + +-+ +-+ + +-+ +-+ +-+ +-+ +-+-+-+ +-+ +-+ +-+-+-+ + +
| | | | | | | | | | | | | |
+ +-+ +-+ + + +-+ +-+ +-+ +-+-+ + +-+-+ +-+ +-+-+ +-+-+ + +-+ + +
| | | | | | | | | | | | | | | | | | | | |
+ + + +-+ +-+-+ +-+ + +-+ +-+ +-+ +-+-+ +-+ + + + + + +-+ +-+-+ +
| | | | | | | | | | | | | | | | | |
+ +-+-+ + + + +-+ +-+ + +-+-+ + +-+ + + + +-+-+-+ + +-+ +-+ +-+ +
| | | | | | | | | | | | | | | | | | | | |
+ +-+ + +-+-+-+-+ + +-+ +-+-+ +-+ + + + +-+ +-+ +-+ +-+ + + +-+-+
| | | | | | | | | | | | | | | | | | | | | | |
+ + +-+ + +-+-+ +-+ + +-+-+ + + +-+-+ +-+-+ + + + + + + +-+-+ +-+
| | | | | | | | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
В целом, язык мне очень понравился. Пусть OCaml по сравнению с Haskell выглядит несколько неказисто, зато мне он кажется куда более прагматичным. Несмотря на свою элегантность, в некоторых вопросах Haskell больно уж академичен. Например, если я хочу изменяемое состояние, Haskell предлагает мне подпорки в виде State и StateT. Нужен контролируемый ввод-вывод? Нет проблем, у нас есть другая подпорка — iteratees. Черт, я просто хочу читать из сокета и иметь изменяемое состояние!
Ставшая уже традиционной подборка ссылок:
- Справка по языку: http://caml.inria.fr/pub/docs/manual-ocaml/;
- Хорошие туториалы: http://mirror.ocamlcore.org/ocaml-tutorial.org/;
- Планета OCaml: http://planet.ocamlcore.org/;
- Книга Introduction to Objective Caml;
- Книга Developing Applications With Objective Caml, есть незаконченный перевод на русский, а также проект по продолжению перевода;
- Документация по OCaml на русском языке: http://ocaml.spb.ru/;
- Список рассылки caml-list: https://sympa.inria.fr/sympa/subscribe/caml-list;
А вы что думаете об OCaml?
Дополнение: Также совсем недавно вышла книга Real World OCaml, которая, по всей видимости, намного актуальнее всех названных выше книг.
Дополнение: См также мою заметку про OCaml Batteries Included.
Метки: OCaml, Функциональное программирование, Языки программирования.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.