У верблюда два горба, потому что жизнь - борьба
На своей нынешней работе я много пишу на OCaml. Не только на нем, но если это не SQL, и не простенькие скрипты, то это почти наверняка будет OCaml. И по результатам трех месяцев я решил сесть и записать свои негативные впечатления от, скажем так, перехода с Haskell. Про позитив писать особого смысла нет - ну, почитаете вы его, покиваете головой и все. А так, глядишь, кто чего посоветует :)
В этот раз, думаю, у меня получится лучше, чем в прошлый.
1 Сигнатуры типов
1.1)Сигнатуры в .ml
В Haskell можно было легко и просто написать у любой функции сигнатуру прямо рядом с реализацией:
В OCaml же такое счастье недоступно. Можно, конечно, извратится:
Но это же как-то противоестественно. Хорошо, если функция определена не через частичное применение других функций - тогда можно явно указать типы аргументов:
А иначе - счастья нет :( Или есть, но я про него не знаю?
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 я это выносить не буду, т.к. это, по большому счету, детский сад и нытье.
На самом же деле:

В этот раз, думаю, у меня получится лучше, чем в прошлый.
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 я это выносить не буду, т.к. это, по большому счету, детский сад и нытье.
На самом же деле:
