вторник, 24 ноября 2009 г.

Made with Common Lisp

Многим людям понравился этот скрин, так что решил поделиться с общественностью :)

На картинке web-приложение (!), редактор планограмм. На клиенте - XUL+SVG+JavaScript (работает только в Firefox). На сервере - Common Lisp (SBCL, Gentoo). Данные о товарах тянутся из MS SQL (1C).


P.S. Желающие посмотреть систему в динамике могут сделать это здесь: http://www.youtube.com/watch?v=5Pm5-TKmPYQ

суббота, 21 ноября 2009 г.

restas-directory-publisher

Выложил на github restas-directory-publisher - плагин для RESTAS, который позволяет публиковать директории. Поддерживается функциональность аналогичная mod_autoindex (Apache) и директиве DirectoryIndex (Apache).

Пример использования.

Я имею архив различной документации в несколько гигабайт, которую собирал несколько лет. Ранее я публиковал её через Apache (у меня также есть самописный файл index.html, в котором прописаны ссылки на все документы), но теперь так сложилось, что на моей домашней машине Apache больше ни для чего другого не используется и я решил, что хватит, пора переходить на Common Lisp решение. В итоге, Apache был выкинут, а его место занял hunchentoot, точнее RESTAS, вот весь код:
(asdf:operate 'asdf:load-op '#:restas-directory-publisher)

(restas:defsite #:homesite
(:use #:cl #:iter))

(in-package #:homesite)

(restas:define-site-plugin devdoc (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("devdoc"))
(restas.directory-publisher:*directory* #P"/path/to/devdoc/"))

(restas:define-site-plugin books (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("books"))
(restas.directory-publisher:*directory* #P"/path/to/books/"))

(restas:start-site '#:homesite :port 8080)

Параметры

Для настройки модуля сейчас можно использовать следующие переменные (все из пакета '#:restas.directory-publisher):
  • *baseurl* - базовый url, по которому публикуется директория
  • *directory* - путь (pathname) к публикуемой директории
  • *directory-index-files* - список Index файлов, по-умолчанию '("index.html" "index.htm")
  • *autoindex* - следует ли автоматически генерировать autoindex, если в директории нет Index-файлов, по-умолчанию t.
  • *autoindex-template* - функция, используемая для отображения autoindex. По-умолчанию - 'restas.directory-publisher.view:autoindex (генерирует autoindex в стиле lighthttpd). Кстати, эта функция определена с помощью cl-closure-template - можно рассматривать в качестве пример использования данной библиотеки :)
Обращаю внимание, что при указании значения переменных внутри макроса define-site-plugin используется динамическое связывание во время обработки запроса, так что один и тот же плагин можно использовать в рамках одного сайта несколько раз с разными параметрами. Примечательно, что код самого плагина об этом ничего не знает и пишется так, как будто он используется глобально. Получаем с одной стороны простоту разработки, а с другой достаточно гибкие возможности повторного использования.

Продвинутое использование

В поставку также входит функция restas.directory-publisher.view:autoindex-content, которую можно использовать для настройки переменной *autoindex-template* - данная функция генерируется только таблицу, а не законченный html. Она может пригодиться для адаптации генерируемого autoindex под внешний вид сайта (на котором используется данный плагин). Использовать эту возможность можно следующим образом:
(defclass homesite-plugin-instance (restas:plugin-instance) ())

(defmethod restas:adopt-route-result ((inst homesite-plugin-instance) (content string))
;;;; здесь встройка content в общий шаблон сайта
...)

(restas:define-site-plugin devdoc (#:restas.directory-publisher homesite-plugin-instance)
(restas.directory-publisher:*baseurl* '("devdoc"))
(restas.directory-publisher:*directory* #P"/path/to/devdoc")
(restas.directory-publisher:*autoindex-template* 'restas.directory-publisher.view:autoindex-content))

вторник, 17 ноября 2009 г.

Зачем нужна Closure Templates

Традиционные шаблоны

Хорошо известная концепция разделения логики приложения и логики представления с помощью шаблонов не работает для AJAX-приложений. Проблема заключается в том, что использование JavaScript для модификации страницы имеет следующие последствия:
  • Код шаблонов должен учитывать, что страница будет модифицироваться с помощью JavaScript, т.е. вы больше не можете отдать шаблоны полностью на откуп дизайнерам, а должны также привлекать и JavaScript-программиста в процессе их разработки
  • JavaScript код должен обладать высокой степенью информированности о структуре страницы
  • JavaScript код должен находиться в полностью согласованном состоянии с кодом шаблонов
  • В случае сложного дизайна, его (JavaScript код) просто сложно писать и каждое изменение в дизайне приложение связано с двойной работой и будет вызывать боль

XSLT

Данные проблемы могла бы решить технология, позволяющая использовать один и тот же код генерации контента как на стороне сервера, так и на стороне клиента. Кроме того, необходима простая возможность генерации как целых страниц, так и её отдельных фрагментов. И такая технология есть уже достаточно давно - это XSLT. Я имею очень хороший (и успешный) многолетний опыт использования XSLT для решения различных задач. И я использую эту технологию в своем основном приложения именно для распределения логики представления между серверной и клиентской частью. И по началу всё шло просто чудесно, но недавно я стал понимать, что имею серьёзные проблемы в этой части. И это при том, что основой приложения является XML REST служба и все данные изначально представлены в XML, а в качестве клиента используется исключительно Firefox: для отображения контента используется XUL и SVG. Т.е. среда для использования XSLT очень даже благоприятная. Кроме того, моя попытки использовать XSLT в более общих web-приложениях не увенчалась успехом: на практике проще обойтись без XSLT. В общем, факт в том, что XSLT не получил действительно широкого распространения в той области, для которой он, собственно, и предназначался. Я вижу следующие причины этого:
  • Тяжеловесность технологии XML. Я имею ввиду, в том числе, что всегда нужно написать достаточно много строчек кода, что бы использовать его даже для простейших операций. Если обмен данными идёт большими кусками, то с этим ещё можно мириться, но когда есть множество функций, оперирующих крохотными кусочками данных, - overhead становится просто невыносим.
  • Ограниченные возможности самого языка преобразований. Он идеально подходит именно для трансформации данных, но на практике часто нужно делать что-то ещё. При использовании XSLT на стороне сервера возможно применения собственных функций расширения, но это не работает на клиенте, что исключает возможность использования расширений в логике представления, работающей как на сервере, так и на клиенте.
  • Различный уровень поддержки технологии со стороны браузеров
  • Проблема дизайна XML-данных. Если он плох (а как показывает практика, далеко не все могут его готовить), то система очень скоро начнёт напоминать монстра, и XML-ненавистники обязательно обратят на это внимание :)
Концептуально XSLT разрабатывался как технология, которая могла быть решить большинство проблем, связанных с логикой представления, но её фактическое состояния, не позволяют говорить об этом всерьёз для широкого круга приложений (что не исключает возможности успешного применения в частных случаях).

Closure Templates

В последнее время, ввиду накопившихся у меня проблем я был занят поиском подходящего решения в это области, и не мог ничего найти, мне ничего не нравилось, не решало моих принципиальных проблем. В сети встречаются описания схем, ориентированных на решение описанных проблем, но они типично сложны в понимании, сложны в технической реализации, плохо масштабируемые, накладывают различные ограничения на структуру приложения т.п.. И тут на помощь пришёл Google, опубликовавший свою библиотеку Closure Templates, которая показалась мне глотком свежего воздуха. Это система шаблонов, которая, как мне представляется, лишена описанных выше недостатков и обладает следующими свойствами:
  • Удивительно простое решение, простое для понимания, простое для использования, не накладывающее никаких ограничений на структуру приложения
  • Один и тот же код, может быть использован как на клиенте, так и на сервере
  • Для использования данной системы нет необходимости в поддержке браузером каких-либо специфичных технологий, поскольку шаблоны компилируются в эффективный JavaScript-код
  • Шаблоны можно разбивать на настолько маленькие компоненты, насколько это необходимо, что даёт простую возможность как генерации больших кусков контента, так и обновления самых небольших фрагментов
  • Входным форматов для шаблонов на стороне клиента, фактически, является JSON
  • Результатом вызова шаблона, в несколько упрощённом виде, является строка
Последние два пункта, между прочим, позволяют легко сочетать данную систему с современными javascript фрэймворками, в частности, я попробовал использовать совместно с jquery и осталось весьма доволен результатом, кажется они просто созданы друг для друга :)

Данная система имеет только один серьёзный недостаток - язык разработки Java :( Но теперь это не проблема, поскольку доступна cl-closure-template - Common Lisp реализация этой идеи (см. мои предыдущие сообщения).

Пример использования

Для cl-closure-template я написал очень небольшое демонстрационное приложение (которое включил в состав исходного кода), этакую "доску сообщений". Пользователи могут заходить и создавать свои сообщения. Сообщения включают в себя информацию об авторе, заголовок и тело. Пользователь может увидеть список всех сообщений, которые при этом отображаются в сокращённом виде (без тела), а также получить "расширенную" версию выбранного сообщения. Все действия производятся в рамках одной страницы, без перезагрузки. Собственно, программа обладает следующим поведением:
  • При загрузке страницы пользователю показывается списком сообщений (для простоты они хранятся в памяти сервера): заголовок и автор
  • Заголовок сообщения обозначается как ссылка, при клике мышкой по нему с сервера асинхронно запрашивается более подробная информация о сообщении, которая преобразуется в html-разметку и происходит замена оригинального html-фрагмента (с сокращенным описание) на сгенерированный
  • Внизу страницы отображается в виде ссылки команда "New message", при клике на которую создаётся (не показывается, а именно создаётся) форма для ввода нового сообщения
  • Пользователь вводит данные и нажимает "Создать": данные формы асинхронно отправляются на сервер, на котором создаётся новый объект и возвращается его описание в формате JSON. На основе возвращённых данных создаётся html-фрагмент для нового сообщения и помещается в начале списка
Для упрощения реализации я использовал jQuery и jQuery Form Plugin. Вся логика представления расположена в одном файле шаблонов: messages.tmpl. В итоге, для реализации описанного выше поведения мне пришлось написать чуть более 30 строк простейшего кода на JavaScript: edit.js. При этом, для большинства случаев разумного изменения дизайна приложения необходимо изменить только файл шаблонов, без необходимости править JavaScript. Весь код приложения можно посмотреть здесь.

понедельник, 16 ноября 2009 г.

JavaScript backend к cl-closure-template

Как я и предполагал, создание JavaScript backend-а для cl-closure-template (моей Common Lisp реализации Closure Template от Google) оказалось делом тривиальным и я уложился в 150 строк кода. Чуть интереснее обстояло дело с тестами, для генерируемого JS. Писать их руками очень не хотелось, но ведь по сути, тесты для CL backend-а и JS backend-а должны быть одинаковы, ведь делает же код одно и тоже, и тестировать его надо одинаково, так родилась мысль использовать общий набор тестов и для CL и для JS версии. Можно было подумать, над какими-нибудь своими макросами для тестов, но всё оказалась проще: для тестов я использую LIFT, который сохраняет всю информацию о тестах в свойствах символов, а это позволило в несколько десятков строк кода полностью автоматизировать процесс создания тестов для JavaScript на основе тестов для Common Lisp. Собственно, я сначала сделал эти тесты, после чего разработка backend-а и стала действительно тривиальной (поправил код, переключился в браузер, посмотрел что не так и т.д.). Сейчас оба backend-а проходят все тесты, так что счёл возможным поставить метку "version-0.1" - можно использовать :) Итого, длительность разработки - 7 дней.

P.S. Интересное наблюдение, SLOCCount оценивает мою работу в 40 000$ (за 7 дней, кто б мне так платил ;)),а оригинальную версию почти в 500 000$. При этом, оригинальная версия пока несколько превосходит мою по функциональности (см. подробности в предыдущем сообщении), но при этом в расчёт стоимости моей версии входят тесты, которые по объёму, но не по сложности написания, составляют почти половину кода. Таким образом, немного поколдовав с калькулятором, можно говорить, что экономически использование Common Lisp примерно в 10 раз более выгодно, чем использование Java, вот как :) ...Ну, конечно, можно сделать и какие-нибудь другие выводы, но мне нравится этот :)

пятница, 13 ноября 2009 г.

Реализовать Closure Template за 5 дней

Я считаю, что мне отчаянно повезло. В тот момент, когда, с одной стороны, проблема распределения логики между серверной и клиентской частью окончательно загнала меня в угол, а с другой очень серьёзно не хватало приемлемой системы шаблонов для Common Lisp, и я со всех сил искал выход, в этот самый момент Google публикует Closure Template. И пусть там, в оригинальном коде, чуть менее 15 000 строк кода (т.е. фактически примерно год разработки, если писать с начала и самостоятельно продумывать дизайн). Главное, что это как раз то решение, которое мне было нужно, а уж реализовать уже готовую спецификацию, да на Common Lisp, - не такая большая проблема. Это удача номер один. Удача номер два - спецификация довольно проста и я сразу понял, что смогу реализовать её малой кровью. Что может быть приятней хорошей и простой в реализации идеи? Правда, при первом прочтении документации меня смутили несколько моментов, их реализация была возможна, но грозила вылиться в нудный и раздутый код, чего я очень не люблю. Но, и в этом удача номер три, абсолютно все эти моменты, меня смутившие, смутили программистов Google не меньше меня, и они аккуратно, не нанося особо большого вреда система (но на мой вкус, несколько подпортив дизайн с точки зрения "высокого программирования" :)), расставили в документации примечания, срезающие все углы и значительно упрощающие реализацию (и это то при их ресурсах? халявщики...). И каждый раз, когда я думал, что здесь может быть сложно, я шёл уточнять в доке и обнаруживал заветное примечание, делающее всё простым, ну не здорово ли? Удача номер четыре? Не думаю, что здесь стоит говорить об удаче - я, конечно, считаю, что Common Lisp безусловно лучший язык для большинства задач, но насколько хорошо именно эта задача легла на CL это просто невероятно, я очень серьёзно впечатлён и эти 5 дней, вполне вероятно, являются лучшими в моей программистской практике.

Итого, спустя пять (!) дней после начала разработки я имею почти законченный (за исключением некоторых мелких деталей, о них ниже) парсер и Common Lisp backend, компилирующий шаблоны в машинный код (на платформах, умеющих это, например на SBCL). Поддерживаются все управляющие конструкции, все указанные в спецификации операторы и функции. Система уже достаточно неплохо отлажена и готова к использованию. Размер исходного код - менее 800 строк кода (без учёта тестов) на Common Lisp (сравниваем с оригинальными 15 000 и делаем какие-нибудь выводы). Какие-либо "навороченные" библиотеки не используются, только регулярные выражения, да штатные средства символьных вычислений, плюс есть зависимость от моей же библиотеки wiki-parser, но в ней то менее 200 строк значимого для данной задачи кода (она также содержит парсер для dokuwiki-разметки).

Есть некоторые отличия, и самое главное это различная реакция на "плохую" разметку. Оригинальная реализация, встречая некорректную конструкцию или выражение ругается матом и указывает на номер строки и позицию в ней, и ещё чего-то там сообщает, ну натуральный компилятор. Подобный уровень парсинга находится вне области моих возможностей, ибо (я уже об этом как-то рассказывал), я совершенно туп в парсинге (возможно, по причине отсутствия математического образования, хотя не могу этого точно утверждать, просто не знаю чему там учат математиков). Термины типа "контекстно-свободная грамматика" или "форма Бэкуса - Наура" вызывают у меня священный ужас и желание перевести разговор на другую тему. Поэтому, для разбора шаблонов я использовал технику, применяемую при разборе wiki-разметок. В итоге, моя реализация правильно обрабатывает все корректные конструкции, а некорректные просто игнорирует и оставляет их в виде простого текста, так что, если накосячить, то результат сразу будет заметен в браузере, чего, на мой взгляд, вполне достаточно.

В данный момент не реализовано два тэга: msg и css - я ещё не вполне разобрался что это такое и какое там должно быть поведение. Но проблема локализации меня в данный момент не очень сильно заботит, поэтому команда msg может некоторое время подождать. Также, не поддерживаются дополнительные атрибуты тэга tempalte. Правда, атрибут autoescape вообще вызывает у меня подозрения: на мой взгляд, подобная обработка должна производиться задолго до попадания данных в шаблон. А вот атрибут private мне очень понравился, он кажется придуман специально для демонстрации подавляющего превосходства Common Lisp над всем остальными языками :), будет обязательно. Также пока не поддерживаются дополнительные директивы команды print. Плюс, литералы float пока можно записывать только самым простым способом, например 3.14, варианты в стиле 6.02e23 пока не поддерживаются. Ну и для строковых литералов также пока не поддерживаются escape-последовательности. Вот, по нереализованному кажется всё. Ну, да, JavaScipt backend-а пока тоже нет, но он будет реализован в самое ближайшее время и ориентировочно будет содержать 200-300 строк, так что общий объём системы будет как раз около 1000 строк кода.

Поскольку разработка подобной системы немыслима без тестов (по крайней мере, я себе это плохо представляю) (странно, но я не нашёл тестов в оригинальном коде Google, плохо искал? это вполне возможно, не силён в Java), так вот, сейчас у меня есть 75 тестов общим размером в 500 строк кода, всё успешно проходятся. Для тестирования использовались следующие реализации: SBCL, Clozure CL, CLISP. Тесты всегда несколько надуманны, поэтому, для пущей надёжности, я перевёл одно из своих приложений (над которым работал в последнее время) на эту систему (ранее там использовалась HTML-TEMPLATE) - каких-либо аномалий выявлено не было, система работает нормально.

В следующий раз я наверное расскажу что же это всё таки за система - Closure Template (мне показалась, судя по обсуждениям на некоторых новостных ресурсах, что она не получила в рунете должной оценки) и почему её стоит сравнивать даже не сколько с традиционными шаблонами, сколько с технологией XSLT (несмотря на то, что как технологии они совершенно различны).


P.S. Ну да, исходный код cl-closure-template распространяется под лицензией Lisp-LGPL и доступен по адресу: http://github.com/archimag/cl-closure-template

понедельник, 9 ноября 2009 г.

Closure Template для Common Lisp

Недавно компания Google открыла исходный код системы Closure Template которая позволяет использовать общий набор шаблонов как с Java, так и с JavaScript. Так получилось, что как раз в этот момент я был озабочен поиском приемлемой системы шаблонов и больше всего меня интересовала возможность использования как на стороне сервера, так и в коде на JavaScript. Я рассмотрел ряд вариантов, но мне ничего не нравилось, а почему-то, такая простая мысль, как генерация javascript-кода на стороне сервера мне в голову никак не приходила (ну может и приходила, но казалась слишком ресурсоёмкой). И тут появляется эта Closure Template.

Исходный код я смотреть не стал (ну что там смотреть, это же Java, раздуто неимоверно, как всегда), вместо этого просмотрел документацию и понял, что это достаточно просто реализовать. И примерно за 8 часов накидал общий каркас решения. Для разбора всего шаблона использую ту же схему, что и для парсинга dokuwiki-страниц (используемую на lisper.ru). Для разбора выражений (типа "$x + $y") взял код из AIMA (http://aima.cs.berkeley.edu/lisp/logic/algorithms/infix.lisp), правда, его надо ещё дотачивать. В данный момент уже могу обрабатывать шаблон "helloName" (из официальных примеров), типа:
CL-USER> (closure-template:translate-template :common-lisp-backend
"{namespace soy.examples.simple} {template hello-name}Hello {$name}{/template}")
(PROGN
(DEFUN SOY.EXAMPLES.SIMPLE:HELLO-NAME (CLOSURE-TEMPLATE::DATA)
(LET ((CLOSURE-TEMPLATE::*TEMPLATE-DATA* CLOSURE-TEMPLATE::DATA))
(WITH-OUTPUT-TO-STRING (CLOSURE-TEMPLATE::*TEMPLATE-OUTPUT*)
(PROGN
(CLOSURE-TEMPLATE::WRITE-TEMPLATE-STRING "Hello ")
(CLOSURE-TEMPLATE::WRITE-TEMPLATE-STRING
(GETF CLOSURE-TEMPLATE::*TEMPLATE-DATA* :NAME)))))))
Либо можно сразу скомпилировать:
CL-USER> (closure-template:compile-template :common-lisp-backend
"{namespace soy.examples.simple} {template hello-name}Hello {$name}{/template}")
SOY.EXAMPLES.SIMPLE:HELLO-NAME
CL-USER> (soy.examples.simple:hello-name '(:name "Andrey"))
"Hello Andrey"
Backend-а к JavaScript ещё нет, но его изготовление, с помощью, parenscript, представляется делом тривиальным.

Сделано пока всё очень топорно и сыро, но общий каркас уже есть (который даже работает на тривиальных примерах). Работа представляется достаточно несложной, думаю, что итоговое решение вряд ли превысит 2000 строк кода (думаю, что будет реально меньше).

Исходный код можно посмотреть здесь: http://github.com/archimag/cl-closure-template

Приглашаются желающие поучаствовать в развитие проекта :)

четверг, 5 ноября 2009 г.

О практичности сообщества Haskell

В последнее время, благодаря стараниям группы товарищей, появилось довольно много разговоров о практичности Haskell. Я ничего не хочу сказать именно о Haskell, ибо прозанималися им всего около месяца, но так и не смог с его помощью подступиться к какой-либо практической задаче и переключился на Common Lisp (да, с Haskell я познакомился раньше, чем с Common Lisp), с которым дело у меня сразу пошло куда веселей. Так вот, мало что могу сказать о практичности Haskell как языка, но практичность его сообщества вызывает большие сомнения. Для демонстрация этого очень подходит обсуждение, предложенной Adept идеи о сравнении языков программирования в условиях, приближенных к боевым. Ценность такого сравнения, вообще говоря, является сомнительной, ну да не суть. Меня поразило, что среди 86-и (на момент написания данного поста) комментариев к этой идеи, в которых предлагаются различные задачи, на которых можно производить сравнение, я обнаружил только одну здравую, обладающую непосредственной практической ценность - "Компрессор CSS файлов для веба" (предложил, что не удивительно, lionet). Всё остальное вызывает, по большей части, только усмешку. Угу. Так вот, я ничего не знаю про практичность языка Haskell, но тот факт, что в сообществе Haskell практичность, как таковая, совершенно отсутствует, очень сильно настораживает...