dastapov: (Default)
Dmitry Astapov ([personal profile] dastapov) wrote2011-07-12 12:11 am
Entry tags:

У верблюда два горба, потому что жизнь - борьба

На своей нынешней работе я много пишу на OCaml. Не только на нем, но если это не SQL, и не простенькие скрипты, то это почти наверняка будет OCaml. И по результатам трех месяцев я решил сесть и записать свои негативные впечатления от, скажем так, перехода с Haskell. Про позитив писать особого смысла нет - ну, почитаете вы его, покиваете головой и все. А так, глядишь, кто чего посоветует :)

В этот раз, думаю, у меня получится лучше, чем в прошлый.

1 Сигнатуры типов
1.1)Сигнатуры в .ml

В Haskell можно было легко и просто написать у любой функции сигнатуру прямо рядом с реализацией:
foo :: String -> Int -> IO ()
foo = bar . baz

В OCaml же такое счастье недоступно. Можно, конечно, извратится:
module type Foo : sig
val foo : string -> int -> ()
end = struct
let foo = ..
end
include Foo

Но это же как-то противоестественно. Хорошо, если функция определена не через частичное применение других функций - тогда можно явно указать типы аргументов:
let foo (x:string) (y:int) = ...

А иначе - счастья нет :( Или есть, но я про него не знаю?

1.2)Сигнатуры у top-level определений.
В Haskell считалось хорошим тоном писать у всех экспортируемых определений типы, чтобы код было проще читать. В OCaml интерфейс вынесен отдельно (в .mli или module type ... : sig) и можно бы сказать, что типы описаны там. Но! Когда читаешь чужой код в .ml, хочется иметь перед глазами типы, а не держать в соседнем окошке .mli и поглядывать туда. Частично ситуацию спасает ocamlspot и интерфейс к нему из emacs - можно поставить курсор на нужное место, нажать C-c C-y и увидеть тип выражения, но это резко снижает скорость чтения кода.

А еще в haskell был хороший режим "ругаться на top-level определения без сигнатур". И компилятор в этом случае говорил: "вот у вас тут foo без типа, это нехорошо. У меня получился тип вот такой: ...".  Это было хорошо и удобно, и без этого как-то плоховато.

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

А еще можно было попросить GHC выписать свое видение списка импортов для всех компилируемых модулей, с четким перечислением того, что откуда реально импортировалось. Временами этого сильно не хватает.

3 Компиляция
3.1) Я не скажу за всю Одессу, и, например, за godi, но я таки скажу за omake.
OMake - это такой make, который умеет много чего, и в частности умеет "автомагически" находить окамловые зависимости. И это работает, и обычно работает неплохо. Но! OMake любит пересобрать зависимости "с запасом". Причем начать пересобирать их издалека. И это, граждане, временами сильно напрягает. А все из-за следующего пункта

3.2)По одному сообщению об ошибках в руки
Возможно, я тормоз, и не дочитал документацию на omake, но в моей практике компилятор выдает мне одно сообщение об ошибке и говорит: "ну все, я так дальше не играю. Чини вот это, и пробуй еще раз". В результате вместо того, чтобы при рефакторинге один раз попробовать откомпилировать, N раз нажать "next error", поправить кучу мест сразу и все собрать, приходится N раз делать "compile - next error - edit - смыть - повторить".

3.3)Сообщения об ошибках
Сообщения об ошибках - это почти всегда "пичалька"
а)"Error: This expression has type foo but is here used with type foo". Происходит это из-за перекрытия определения более поздним (shadowing) и выбивает из колеи только в первый раз, но все равно - горсть нервных клеток оно у меня забрало :)
b)Когда типы в реализации модуля не совпадают с интерфейсом, компилятор считает своим долгов вывалить в сообщение об ошибке весь интерфейс модуля, сколько его ни есть
c)Еще очень бодрит, когда компилятор в сообщении об ошибке цитирует грамматику языка, типа: Error("[fun_def] expected after [simple_patt] (in [expr])"). Сразу становится понятно, что не так, и что делать :)

4 Синтаксис
4.1)Ну, про вложенные match и точки с запятой не писал только ленивый. Я ленивый, поэтому повторятся не буду.

А напишу я лучше про ... комментарии.

Если так случилось, что ваши исходники собираются с помощью caml4p, и у вас только что случился merge conflict при обновлении исходников из репозитория, но по счастливой случайности он случился только в тексте комментариев - рано радоваться. Если вы решили сначала устранить все ошибки в исходниках, а потом разобраться с комментариями, как это попытался сделать я, то вас может ожидать неприятный сюрприз: caml4p будет возбуждаться на "<<<<<" и ">>>>>>" в тексте комментариев и в результате совершенно правильный код не будет собираться с совершенно невнятными ошибками.

Это, право слово, был удар ниже пояса :)

4.2)[REDACTED]

4.3)Синтаксического сахара для монад в языке нет. Но так как монады удобны, их используют. В результате имеем кучу boilerplate кода для monadic binds, из-за которого, опять же, тяжело читать код.

4.4)Читать типы справа налево - это иезуитство, и меня до сих пор временами клинит. Предложение о том, чтобы писать их слева направо запихнули в совершенно упадочный по своей сути Revised Syntax, где благополучно и похоронили.

4 Функторы
Функторы - это круто, но если интерфейс к твоему коду - это функтор (смотрим на Set, Hashtbl и проч. из Core), то единственный способ написать свою функцию, которая будет работать с любым Set-ом -- это нагородить поверх еще один функтор. В резлутьтате получается "ехал функтор через функтор, видит функтор - функтор(функтор), сунул функтор функтор в функтор - функтор функтор функтор функтор!".

5 Монады

В ru_lambda я это выносить не буду, т.к. это, по большому счету, детский сад и нытье.

На самом же деле:

Post a comment in response:

If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting