Пытаюсь изучать 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. Посмотреть исходный код полностью вы можете в репозитории на BitBucket.

(* Ячейка лабиринта *)
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.

Кстати, пользуясь случаем, вновь призываю вас поддержать нашу кампанию на BoomStarter по сбору средств на запись второго сезона EaxCast. Поддержать можно как рублем, так и рассказав о кампании друзьям. Помогите нам сделать хороший, годный программерский подкаст!

Метки: , , .

Подпишитесь на блог с помощью RSS, E-Mail, Google+ или Twitter.

Понравился пост? Поделитесь с другими: