Давно собирался написать нечто подобное, да всё руки не доходили. Толчком послужил краткий пост на аналогичную тему у моего знакомого.
Как известно, JavaScript — язык c динамической (для классов с утиной) типизацией, с очень плохо развитой системой типов. Стандартный интерпретатор даже в браузерах Mozilla с расширениями разработчика отлавливает минимум ошибок. Практически все эти ошибки — синтаксические. В результате, писать надёжный код на JavaScript предельно сложно. Положение усугубляет фактическая невозможность запуска программы без использования браузера, т.е. нет никакой песочницы для тестов.
В этой статье я попытаюсь описать некоторые технологии, которые помогут знакомому с Ocaml человеку существенно сократить время написания безопасного, стабильного и более-менее компактного JS-кода. Впрочем, описываемые техники есть и для некоторых других языков с развитой системой типов (например, Haskell).
Ocamljs: компилятор Ocaml ⇒ JavaScript
Скачать: http://code.google.com/p/ocamljs/
Всё просто: вы пишете client-side код вашего сайта на Ocaml, а с помощью этого компилятора получаете готовый для использования в браузере JavaScript. Для обеспечения работы, непосредственно к вашему скрипту дописывается около 40KB дополнительного кода. Например. Ни к чему не обязывающий код:
type tree = | Leaf of int | Node of tree list let l : tree list = [] let _ = match l with | [] -> Dom.window#alert "empty list" | a :: [] -> () | a :: b :: [] -> () | _ -> Dom.window#alert "unexpected list"
Компилируем:
ocamljs -o test.js -I /usr/local/lib/ocaml/3.10.2/dom/ dom.cmjsa test.ml
Получаем вот такой код:
// ..... var oc$Dom$ = function () { var window$717 = window; var document$718 = document; return $(window$717, document$718); }(); var oc$1$ = function () { var l$63 = 0; if (l$63) { var match$71 = l$63[1]; if (match$71) if (match$71[1]) (function () { var v$74 = oc$Dom$[0]; return _m(v$74.alert, v$74, ["unexpected list"]); }()); else ; else ; } else (function () { var v$73 = oc$Dom$[0]; return _m(v$73.alert, v$73, ["empty list"]); }()); return $(l$63); }(); var oc$Std_exit$ = (_(oc$Pervasives$[80], [0]), $()); return caml_named_value; })();
JavaScript compressor
Как я уже сказал, код получается достаточно объёмным. Частично эту проблему решает gzip (при условии, что и сервер, и браузер имеют его поддержку). Однако, код можно существенно сократить за счёт оптимизации по размеру. Для этого есть ряд готовых online-утилит. Используемые техники:
- Удаление комментариев
- Удаление не несущих синтаксической нагрузки пробелов и переносов строк
- Назначение локальным переменным функций более коротких имён
- Вынесение длинных повторяющихся констант в переменную с коротким именем
Наилучшие результаты показывает Packer (http://dean.edwards.name/packer/), однако ocamljs компилирует не полностью корректный код — иногда забывает точки с запятой — поэтому Packer генерирует не работающий код. С учётом некорректности оригинального кода, наилучший результат показал Dojo ShrinkSafe (http://www.dojotoolkit.org/docs/shrinksafe). Рекомендую прогнать код через валидатор (http://jslint.com/), это в любом случае неплохая идея. Пример кода, получающегося с помощью Dojo ShrinkSafe:
// ..... var _213=function(){ var l$63=0; if(l$63){ var _215=l$63[1]; if(_215){ if(_215[1]){ (function(){ var v$74=_210[0]; return _m(v$74.alert,v$74,["unexpected list"]); }()); }else{ } }else{ } }else{ (function(){ var v$73=_210[0]; return _m(v$73.alert,v$73,["empty list"]); }()); } return $(l$63); }(); var _218=(_(_106[80],[0]),$()); return _9d; })();
Код получается практически не читаемым, зато размер сокращается в 2-4 раза.
Быстро сравнить различные компрессоры можно на сайте: http://compressorrater.thruhere.net/
O’Browser: виртуальная машина на JavaScript
Скачать: http://ocsigen.org/obrowser/
Ваш код будет скомпилирован в байт-код, закодированный в UUE. Для вызова кода, достаточно вызвать его в виртуальной машине:
<script src="vm.js" type="text/javascript"></script> <script type="text/javascript"> /* <![CDATA[ */ window.onload = function () { exec_caml ("tutorial.exe.uue") ; } /* ]]> */ </script>
Байт-код получается достаточно объёмным, да и сжатию поддаётся только за счёт слоя gzip. Впрочем, код самой виртуальной машины вполне поддаётся обработке JavaScript компрессоров. Валидность кода получается даже выше, чем у ocamljs. Декомпиляция байт-кода в нормальный JavaScript становится нерешаемой проблемой :)
#1 by yumaa on October 7th, 2009
имею некоторый опыт по написанию скриптов на javascript, и имхо валидность кода ещё пол-дела. и если код на ocaml очарователен по виду, но получившийся в результате ocamljs скрипт – страшный как моя жизнь. логическую ошибку искать в таком… при одной мысли в дрожь бросает :) или ещё чего хуже, под разные браузеры править, чтобы работало одинаково.
#2 by John Lepikhin on October 7th, 2009
Теперь сюрприз: сделать логическую ошибку на языке с хорошо развитой системой типов очень сложно :) С использованием OUnit (система для написания юнит-тестов) ошибку допустить предельно сложно. С проектированием алгоритмов в каком-нибудь Coq и вовсе невозможно.
К слову, в oProxy было за всю историю лишь 2-3 логических ошибки(!). И то, саппорт их не видел. Их заметил только я т.к. они были минорными. А то, из-за чего имел проблемы саппорт — это не логические ошибки, а ошибки в моём ДНК:
1. Апрельская проблема: не надо использовать POSIX-потоки. Они очень тяжёлые.
2. Сентябрьские дауны: плохо давать процессу ничем не ограниченное количество файлов. В драке за FD процесса и ОС всегда почему-то побеждает ОС.
Согласись, обе ошибки к алгоритмической логике отношения не имеют.
Касаетельно браузерной совместимости. Так ведь и на окамле точно так же всё можно описать. Тем более, что таких узких мест на большой скрипт обычно очень мало: бОльшая часть скрипта всё равно будет алгоритмом, RPC и т.д.
#3 by RedChrom on October 9th, 2009
Всё-таки с ocamljs есть проблемы с типами, точнее в его интерфейсе к внешнему миру. Ибо некоторые js функции могут вернуть null, вместо element и это дело нужно явно чекать. Тем не менее если обвернуть более высокоуровневый и безопасный интерфейс – жить можно.
>бОльшая часть скрипта всё равно будет алгоритмом, RPC и т.д.
Вот главная минус всяких jQuery, это предположение, что весь DOM свёрстан ручками, а надо только свистелки и перделки добавлять по идентификаторам. У меня весь DOM из js строиться и еботни со этим DOM дофига. Манипуляции с DOM из ocaml очень не привычны, но привыкаешь и ошибок действительно получается меньше.
#4 by RedChrom on October 9th, 2009
Btw, если кто собирётся писать что-то серьёзное на ocamljs то ко мне можно обращаться за патчами, ибо хуй знает когда в апстриме их закоммитят :)
#5 by John Lepikhin on October 9th, 2009
Что патчил?
#6 by RedChrom on October 9th, 2009
Ocamljs, там не хватает некторых dom properties, проблемы с юникодом и так по мелочи.