Пытаюсь изучать 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:

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.

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.