Сегодня я хочу прервать долгую тишину в эфире и написать объективное и непредвзятое сравнение отсталого и отстойного компилятора ocaml с современными и прогрессивными компиляторами для всех остальных языков.
Что же плохо в компиляторе ocaml (заодно пройдемся и по интерпретатору)?
Претензия номер раз: он не умеет компилировать проекты из нескольких файлов. Допустим, у нас есть foo.ml и bar.ml следующего содержания:
Попробуем поставить себя на место новичка в ocaml и скомпилировать их. Так как основной модуль у нас bar, компилируем его:
Вопрос: что мешает компилятору немного проявить немного инициативы и найти у себя под носом модуль foo и скомпилировать его, раз уж его недостает? Вроде на дворе у нас 21-ый век ... Ну ладно, мы не гордые - укажем модуль вручную:
Увы и ах, компилятор надо кормить с ложечки, подавая файлы в правильном порядке - все зависимости должны быть указаны до модулей, которые их используют. Не компиляторово это дело - заниматься топологической сортировкой:
Ура! Заработало! Теперь разберемся с раздельной компиляцией.
Претензия номер два: раздельная компиляция сделана через зад.
Может, раз совместная компиляция модулей foo и bar - это такая проблема, стоит собирать их по отдельности, а потом линковать? А вот фиг - проще не будет!
То есть, раздельную компиляцию нужно выполнять тоже с учетом топологической сортировки по зависимостям.
Кроме того, на пути заботливо разложены дополнительные грабли под названием cross-module inlining и присыпаны листиками. Давайте вручную опишем интерфейс модуля foo и скомпилируем все раздельно:
Теперь вручную изменим реализацию "foo", не меняя интерфейс, напимер добавим туда неэкспортируемое определение "let boo = 666". Теперь скомпилируем модуль "foo" (только реализацию - ведь интерфейс-то мы не меняли) и перелинкуем программу:
Казалось бы, что за нафиг? А все дело в том, что в процессе компиляции "bar.ml" компилятор увидел файл "foo.cmx" и подумал: "О! А почему бы не попробовать сделать cross-module inlining?". В результате в bar.cmx осталась ссылка на sha1(?) хеш foo.cmx. Именно это приводит к проблемам в линковке.
Однако, если перед вызовом "ocamlopt -c bar.ml" убрать foo.cmx куда-то в сторонку, а потом вернуть его обратно, все скомпилируется, cross-module inlining не выполнится и потом можно будет перекомпилировать foo.ml отдельно от всего остального и перелинковать программу без всяких проблем.
Что тут плохо:
1)Если сначала все собрать без inlining-а, а потом в какой-то момент перекомпилировать bar.ml, компилятор выполнит inlining
2)Нельзя получить хоть какую-то диагностику от компилятора о том, делается ли inlinining или нет
3)Нельзя запретить(!) inlining. Опция "inline=0", вопреки ожиданиям, не запрещает inlining, просто делает его очень "безинициативным"
Печальное следствие 1: если хочется для ускорения разработки наплевать на скорость скомпилированного кода и уменьшить количество пересобираемых модулей путем отключения inlining-а, надо очень сильно извращаться.
Печальное следствие 2: нет простого способа компилировать код на ocaml. Если писать для своей системы сборки правила вида "ocamlopt -o prog.exe foo.ml bar.ml baz.ml", то придется руками переставлять имена модулей туда-сюда при любом изменении графа зависимостей. А неявные правила (implicit rules) вида "%.cmx: %.ml" нельзя использовать без механизма обнаружения зависимоей (об этом - позже). Нельзя также наплевать на компиляцию вообще и сказать "да хрен с ним, проинтерпретируйте мой код" (об этом тоже подробнее будет позже).
Я подозреваю, что именно вот эта необходимость лепить нетривиальные правила сборки даже для самых тривиальных проектов отвратила от ocaml ооочень много людей.
Продолжение следует, у меня накипело :)
Что же плохо в компиляторе ocaml (заодно пройдемся и по интерпретатору)?
Претензия номер раз: он не умеет компилировать проекты из нескольких файлов. Допустим, у нас есть foo.ml и bar.ml следующего содержания:
foo.ml
======
let foo = 42;;
bar.ml
======
let bar = Foo.foo + 1;;
let () =
Printf.printf "bar = %d\n" bar;;
Попробуем поставить себя на место новичка в ocaml и скомпилировать их. Так как основной модуль у нас bar, компилируем его:
adept> ocamlopt bar.ml File "bar.ml", line 1, characters 10-17: Error: Unbound module Foo
Вопрос: что мешает компилятору немного проявить немного инициативы и найти у себя под носом модуль foo и скомпилировать его, раз уж его недостает? Вроде на дворе у нас 21-ый век ... Ну ладно, мы не гордые - укажем модуль вручную:
adept> ocamlopt bar.ml foo.ml File "bar.ml", line 1, characters 10-17: Error: Unbound module Foo
Увы и ах, компилятор надо кормить с ложечки, подавая файлы в правильном порядке - все зависимости должны быть указаны до модулей, которые их используют. Не компиляторово это дело - заниматься топологической сортировкой:
adept> ocamlopt foo.ml bar.ml adept> ./a.out bar = 43
Ура! Заработало! Теперь разберемся с раздельной компиляцией.
Претензия номер два: раздельная компиляция сделана через зад.
Может, раз совместная компиляция модулей foo и bar - это такая проблема, стоит собирать их по отдельности, а потом линковать? А вот фиг - проще не будет!
adept> ocamlopt -c bar.ml File "bar.ml", line 1, characters 10-17: Error: Unbound module Foo
То есть, раздельную компиляцию нужно выполнять тоже с учетом топологической сортировки по зависимостям.
Кроме того, на пути заботливо разложены дополнительные грабли под названием cross-module inlining и присыпаны листиками. Давайте вручную опишем интерфейс модуля foo и скомпилируем все раздельно:
[2] adept> cat foo.mli val foo : int;; adept> ocamlopt -c foo.mli adept> ocamlopt -c foo.ml adept> ocamlopt -c bar.ml adept> ocamlopt foo.cmx bar.cmx
Теперь вручную изменим реализацию "foo", не меняя интерфейс, напимер добавим туда неэкспортируемое определение "let boo = 666". Теперь скомпилируем модуль "foo" (только реализацию - ведь интерфейс-то мы не меняли) и перелинкуем программу:
adept> ocamlopt -c foo.ml
adept> ocamlopt foo.cmx bar.cmx
File "_none_", line 1, characters 0-1:
Error: Files bar.cmx and foo.cmx
make inconsistent assumptions over implementation Foo
Казалось бы, что за нафиг? А все дело в том, что в процессе компиляции "bar.ml" компилятор увидел файл "foo.cmx" и подумал: "О! А почему бы не попробовать сделать cross-module inlining?". В результате в bar.cmx осталась ссылка на sha1(?) хеш foo.cmx. Именно это приводит к проблемам в линковке.
Однако, если перед вызовом "ocamlopt -c bar.ml" убрать foo.cmx куда-то в сторонку, а потом вернуть его обратно, все скомпилируется, cross-module inlining не выполнится и потом можно будет перекомпилировать foo.ml отдельно от всего остального и перелинковать программу без всяких проблем.
Что тут плохо:
1)Если сначала все собрать без inlining-а, а потом в какой-то момент перекомпилировать bar.ml, компилятор выполнит inlining
2)Нельзя получить хоть какую-то диагностику от компилятора о том, делается ли inlinining или нет
3)Нельзя запретить(!) inlining. Опция "inline=0", вопреки ожиданиям, не запрещает inlining, просто делает его очень "безинициативным"
Печальное следствие 1: если хочется для ускорения разработки наплевать на скорость скомпилированного кода и уменьшить количество пересобираемых модулей путем отключения inlining-а, надо очень сильно извращаться.
Печальное следствие 2: нет простого способа компилировать код на ocaml. Если писать для своей системы сборки правила вида "ocamlopt -o prog.exe foo.ml bar.ml baz.ml", то придется руками переставлять имена модулей туда-сюда при любом изменении графа зависимостей. А неявные правила (implicit rules) вида "%.cmx: %.ml" нельзя использовать без механизма обнаружения зависимоей (об этом - позже). Нельзя также наплевать на компиляцию вообще и сказать "да хрен с ним, проинтерпретируйте мой код" (об этом тоже подробнее будет позже).
Я подозреваю, что именно вот эта необходимость лепить нетривиальные правила сборки даже для самых тривиальных проектов отвратила от ocaml ооочень много людей.
Продолжение следует, у меня накипело :)
(no subject)
Date: 2011-11-10 09:08 am (UTC)Инлайнинг отключать/запрещать не надо.
(no subject)
Date: 2011-11-10 09:14 am (UTC)2)Страшно мало мест, в который написано "сразу используйте ocamlbuild". В частности, оф. документация про него молчок. Вот это уже гораздо хуже
2)Как только ситуация становится чуть более сложной и ocamlbuild не может справится с ней с дефолтными правилами, он становится неудобен. См, например, http://brion.inria.fr/gallium/index.php/Ocamlbuild_example_with_C_stubs. То есть, если начать с ocamlbuild, то потом сложно масштабироваться.
(no subject)
Date: 2011-11-10 09:20 am (UTC)2. Мест -- мало, но чтение рассылки и минимальное разглядывание нормальных библиотек вполне даёт представление о нём. И да, он появился относительно недавно, поэтому ситуация вполне объяснима.
3. ocamlbuild можно докрутить в любую нужную сторону. Собственно, ссылка как раз демонстрирует это. И это -- в среднем чище, чем лепить свои мейкфайлы или использовать ocamlmakefile какой-нибудь. (проверял.)
(no subject)
Date: 2011-11-10 09:39 am (UTC)2. Тут получается как в известном анекдоте про ложечки - они нашлись, а осадок неприятный уже остался.
(no subject)
Date: 2011-11-10 06:08 pm (UTC)(no subject)
Date: 2011-11-10 07:03 pm (UTC)(no subject)
Date: 2011-11-10 07:32 pm (UTC)(no subject)
Date: 2011-11-10 07:43 pm (UTC)2. варианты были самые очевидные, не полный список, конечно. Не нужно много ума, чтобы посмотреть, кто, как и чем собирает окамловские проекты. Это реально очень просто, очень тупо. Если бы у меня возник вопрос -- я бы посмотрел, как белые люди это делают. Есть даже специальные камлунити для того, чтобы это просто и быстро выяснить у живых людей, которые, может, ещё что хорошее посоветуют.
Утилита ocamlbuild -- не является нужной в смысле необходимости. Без неё можно, но она является удобной, и даже очень. Например, когда я смотрел на тот же эрланг, я как-то не встречал ничего про dyalizer -- а ведь тоже нужная утилита. Потом, конечно, подсказали про него, но, всё же, я же не возмущался с таким удивлением, как будто от меня скрыли тайну партии и всего такого.
(no subject)
Date: 2011-11-10 10:32 pm (UTC)Ну а как живётся с make без ocamlbuild, мы недавно заслушали.
Что касается dialyzer, то вы невнимательно читали официальную документацию :-) См. http://www.erlang.org/doc/man/
(no subject)
Date: 2011-11-10 11:11 pm (UTC)(no subject)
Date: 2011-11-10 11:16 pm (UTC)(no subject)
Date: 2011-11-10 11:23 pm (UTC)(no subject)
Date: 2011-11-10 11:25 pm (UTC)> неправильно, конечно.
Тогда я вас не понимаю.
(no subject)
Date: 2011-11-10 10:36 pm (UTC)1. Так, значит, информацию об окамловских библиотеках тоже в Интернете искать не надо? Тем более неудобно то, что в официальной документации ничего не сказано про основное средство для сборки (хотя я сам её не читал, но верю окружающим на слово, что ничего таки не сказано).
(no subject)
Date: 2011-11-10 09:15 am (UTC)Скажу, что в фадиезе не лучше. Там даже тулы а-ля ocamldep нет, надо файлы сортировать руками. И конпелирует он проект целиком. Поменял один файл --- перекомпилировать надо вместе с отсальными. долго-долго перкомпилировать.
(no subject)
Date: 2011-11-10 09:15 am (UTC)Про F# - спасибо, был не в курсе, теперь буду знать.
(no subject)
Date: 2011-11-10 09:49 am (UTC)Распилить проект на assembly помогает, наполовину - если собирать корневую то пересоберутся все, т.к. зависимость по датам изменения dll, а не по внешнему интерфейсу. Это впрочем лечится неделей возни с MSBuild вечерами.
(no subject)
Date: 2011-11-10 01:35 pm (UTC)(no subject)
Date: 2011-11-10 04:34 pm (UTC)Я хочу время инкрементальной компиляции, сопоставимое с внятным С++ проектом на внятном тулчейне - любое изменение реализации приводит к билду длительностью в пару секунд.
На работе в С++-ной кодобазе на PS3 вот так - при том что вся линковка статическая.
В .NET даже в C# этот лимит достигается на проекте с количеством кода в разы меньшем чем в рабочем, в F# еще хуже.