![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Сегодня я хочу прервать долгую тишину в эфире и написать объективное и непредвзятое сравнение отсталого и отстойного компилятора 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 ооочень много людей.
Продолжение следует, у меня накипело :)