вторник, 29 декабря 2009 г.

Странности в gentoo-lisp-overlay

Что-то стали в gentoo-lisp-overlay кривые пакеты попадаться: для bordeaux-threads-0.7.0 файл с версией не копируется, для parenscript-20090921 зависимости не указаны... Как они их делаю?.. Точнее, как делают понятно, но почему не проверяют - вот это мне не понятно. И исправлений никаких уже долго нет, один я что-ли им пользуюсь... Эх, фиксю и помещаю исправления в свой форк.

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

restas-directory-publisher на lisper.ru

Больше для демонстрации, чем из реальной необходимости, включил пример использования плагина restas-directory-publisher в состав сайта lispe.ru, смотреть здесь.

Код тривиален:
(restas:define-site-plugin rulisp-files (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("files"))
(restas.directory-publisher:*directory* (merge-pathnames "files/" *vardir*))
(restas.directory-publisher:*autoindex-template*
(lambda (data)
(rulisp.view.fine:main-frame (list :title (getf data :title)
:css (css-files-data '("style.css" "autoindex.css"))
:user (compute-user-login-name)
:main-menu (main-menu-data)
:content (restas.directory-publisher.view:autoindex-content data)
:callback (hunchentoot:request-uri*))))))

среда, 23 декабря 2009 г.

Элементы функциональных языков. Резюме.

Дочитал таки статью "Элементы функциональных языков", мысль по прочтению только одна: если бы хаскелисты освоили Common Lisp, то большинство из них забыло бы про Haskell как про страшный сон :)

Динамическая область видимости

Прочитал в последнем номере журнала "Практика функционального программирования" в статье про "Элементы функциональных языков" буквально следующее:
В языке LISP присутствовали замыкания с динамической областью видимости, нарушавшие законы лямбда-исчисления и, вообще говоря, неудобные в использовании. Emacs LISP— практически единственный используемый на практике современный язык программирования, где используется динамическая область видимости.
Т.е. тут два посыла:
  • Динамическое область видимости неудобна
  • Динамическая область видимости осталась только в ELisp
Теперь вот сижу, пытаюсь найти мотивацию для дальнейшего прочтения, это же надо, так высказаться об одном из ключевых и наиболее удобных свойств Common Lisp: динамические переменные это потрясающе удобно и поэтому используются в коде на Common Lisp чрезвычайно широко.

вторник, 22 декабря 2009 г.

И ещё пара слов о Russian Lambda Planet

Когда проект Russian Lambda Planet был только запущен, это было даже интересно, там было мало блогов, много о ФП и я добавил её в свой RSS-реадер. Месяца три назад я оттуда её удалил, ибо проект превратился неизвестно во что. Куча блогов, в которых теперь очень мало пишут про ФП, зачем-то агрегируются в одну ленту, тему которой, иначе как "Мои ЖЖ друзья", теперь-то и определить сложно.

Почему бы не отбирать сообщения по тэгам? Может движок не позволят? Ну тогда, почему бы не переписать её на "великом и ужасном" Erlang? вроде как и фрэймворки для веба есть... Или даже на Haskell? вдруг и на нём можно подобное писать (в этом, я правда сильно сомневаюсь)?

При чём, работы то там на один вечер. Для сравнения, размер исходного кода Russian Lisp Planet - 240 строк. Ведь все же видят, что в планете сплошной мусор. Переписали бы на Erlang, вот и материал для журнала: "вот сайт, вот код, вот инструкция по установке, а делали мы это так...". И не надо будет больше читать про то, что "GSM убивает тараканов" (с). Нет же, учавствовать в бесконечных холиварах желающих толпы, а как код писать - так у всех "почасовая оплата" (с), ну что за народ...

colorize

Для подсветки кода сейчас для Common Lisp есть пакет colorize, который не обновлялся с 2004-года (но svn-сервер с ним работает исправно, хе, но там только один коммит). С другой стороны, есть приложение lisppaste, которое включает в себя colorize, обращаю внимание, что не использует, а именно включает, т.е. оба этих пакета имеют общий набор файлов. Вообще, ситуация довольно дикая, я не стал вникать в историю (если она даже где-то описана), но подобный подход разработчиков представляется мне довольно безответственным. Так вот, в lisppaste в данный момент поддерживает несколько больший набор языков, чем colorize (сам выбор этих языков представляется мне довольно странным, здесь можно опять порассуждать о безответственности). А ведь, этот функционал является довольно важным, если вы хотите разрабатывать на Common Lisp приложения типа форума, вики, pastebin и т.п.

В общем, я взял colorize, взял изменения из lisppaste, и создал отдельный репозиторий: http://github.com/archimag/colorize. Желающие добавить поддержку новых языков могут слать мне патчи :)

Да, есть один нюанс. Для правильного форматирования кода на Common Lisp пакету требуется установленный HyperSpec. В оригинальном коде он ищётся в домашнем каталоге, но меня такой подход не впечатлил. Я сделал как в Gentoo, где HyperSpec размещается в каталоге #P"/usr/share/doc/hyperspec-7.0/HyperSpec/" - если у вас он находитсья в другом месте, то необходимо открыть файл clhs-lookup.lisp и изменить в нём значение *hyperspec-pathname*.

P.S. Проделав всё это захотел проверить как colorize будет обрабатывать код на теперь поддерживаемых Erlang или Haskell. Думаю, где же искать этот код, если не на Russian Lambda Planet? - пошёл туда, ага, как же. На Russian Lambda Planet есть всякие забавные фотки (кошечки или там разные предметы мебели), есть разная болтовня про мотоциклы, или даже я видел про баб, есть немного словоблудия про ФП в основном в виде ссылок с восторженными комментариями, но там совершенно нет кода на функциональных языках. Нет, справедливости ради, должен заметить, что иногда там всё таки бывает код на Erlang или Haskell (когда-то я там его видел), но кажется что кода на C или Java там значительно больше. Я думаю, что редакция журнала "Практика функционального программирования" несколько погорячилась с этим конкурсом - если какие решения и появятся, то скорей всего это будет Java или PHP :), уж очень не верится, что люди, столько неохотно пишущие код, сподобятся написать что-либо более-менее серьёзное... тем более, столь бесполезное для большинства...

воскресенье, 20 декабря 2009 г.

restas-colorize

Закончил отделение кода "форматтера кода" (обычно подобные приложения называют pastebin) от исходного кода lisper.ru и сейчас он доступен как отдельный restas-плагин: restas-colorize. Перевёл lisper.ru на использование этого плагина. Как побочный результат - новая возможность, теперь можно форматировать код не только на Common Lisp, поддерживаются и некоторые другие языки программирования. Планирую в будущем расширить список поддерживаемых языков, но это не имеет отношения к restas-colorize, а только к библиотеке colorize.

Сейчас для настройки плагина можно использовать следующие переменные (из пакета #:restas-colorize):
  • *max-on-page* - при просмотре списка "pastes" определяет максимальное их количество на одной странице
  • *finalize-page* - служит для "встраивания" плагина в сайт, задания общего оформления
  • *colorize-user-function* - плагин не имеет своей системы авторизации, предпочитает использовать внешнюю
  • *storage* - пожалуй самый интересный параметр, в состав плагина входит только хранилище в памяти, которое позволяет хранить "pastes" в памяти только во время выполнения. Для реального сайта необходимо, скорей всего, использовать хранение в базе, но плагин не берётся судить о том, какая структура базы будет оптимальной для всех сайтов, поэтому на стороне сайта необходимо создать объект *storage*, реализующий требуемый протокол и передать в этой переменной в плагин.
На lisper.ru данный плагин сейчас подключается следующим образом:
(restas:define-site-plugin rulisp-format (#:restas.colorize)
(restas.colorize:*baseurl* '("apps" "format"))
(restas.colorize:*max-on-page* 15)
(restas.colorize:*storage* *rulisp-db-storage*)
(restas.colorize:*colorize-user-function* #'compute-user-login-name)
(restas.colorize:*finalize-page* (lambda (content)
(rulisp.view.fine:main-frame (list :title (getf content :title)
:css (css-files-data '("style.css" "colorize.css"))
:user (compute-user-login-name)
:main-menu (main-menu-data)
:content (getf content :content)
:callback (hunchentoot:request-uri*))))))
Посмотреть используемую реализацию storage можно здесь.

четверг, 17 декабря 2009 г.

20 000 строк кода на Common Lisp

Подсчитал с помощь SLOCCount, что за последние полтора года написал на Common Lisp чуть менее 20 000 строк кода, половина - открытый код.

среда, 16 декабря 2009 г.

restas-directory-publisher и cgi-скрипты

Добавил к restas-directory-publisher возможность исполнять cgi-скрипты, которую предоставляет пакет hunchentoot-cgi. Запустить, например, веб-морду к git на основе gitweb.cgi (который поставляется вместе с git) сейчас можно следующим образом:
(asdf:operate 'asdf:load-op '#:restas-directory-publisher)

(restas:defsite #:gitweb
(:use #:cl))

(in-package #:gitweb)

(restas:define-site-plugin git (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("gitweb"))
(restas.directory-publisher:*directory* #P"/usr/share/git/gitweb/")
(restas.directory-publisher:*enable-cgi-by-type* '("cgi")))

(restas:start-site '#:gitweb :port 8080)
Теперь открываем в браузере адрес http://localhost:8080/gitweb/gitweb.cgi и видим знакомый, полностью функционирующий интерфейс.

Это даже проще, чем настроить Apache для решения данной задачи :)

git-репозиторий с hunchentoot

С исходным кодом Hunchentoot невозможно работать, ибо он лежит под Subversion. Это просто ненормально, 2010-й год на носу. Ладно, теперь есть и под git: http://github.com/archimag/hunchentoot. Обязуюсь регулярно синхронизировать с основным репозиторием и не вносить каких-либо своих изменений. Т.е. это будет чистая копия. Может кто-нибудь форкнет, в конце-то концов...

www-restas и cl-gtk2

Внёс изменения в свой форк gentoo-lisp-overlay: добавил новую категорию www-restas и перенёс в неё пакеты restas-planet, restas-wiki и restas-directory-publisher.

Кроме того, добавил пакеты из cl-gtk2-overlay, который поддерживает dmitry_vk: всё таки git мощная штука :)

вторник, 15 декабря 2009 г.

RESTAS-daemon

Ранее я рассказывал, как можно написать демона на "pure lisp" и таким образом обойтись без граблей в виде Screen или detachtty. С тех пор, у меня развелось некоторое колличество подобных демонов для разных сервисов, с общим базовым кодом и незначительными отличиями. Я использовал технику copy/paste, что меня сильно огорчало, да и неудобства заметные - время от времени я правлю базовый код и изменения надо руками разносить по разным файлам (на разных серверах). Желание сделать некую общую схему с повторным использованием у меня было с самого начала, но вот сделать универсальный "демонизатор" не так просто. В итоге, я решил отказаться от универсального решения, и сделать скрипт для запуска как демонов web-приложений на базе RESTAS. Это довольно просто, ибо все эти приложения запускаются однотипно: загрузить нужную asdf-систему и вызвать restas:start-site с именем сайта, определенным с помощью макроса restas:defsite. Получившийся скрипт я назвал restas-daemon.lisp и положил в директорию contrib пакета RESTAS. Теперь запустить демона для web-приложения на базе RESTAS очень просто:
sbcl --noinform --no-userinit --no-sysinit --load /path/to/restas/contrib/restas-daemon.lisp /path/to/daemon.conf COMMAND
Как видно, данный скрипт принимает два параметра: путь к файлу конфигурации демона и имя команды. Поддерживаются следующие команды:
  • start - запуск
  • stop - остановка
  • restart - рестарт
  • kill - убийство, если почему-то демон не хочет останавливаться с помощью stop
  • zap - удалят pid-файл, это может потребоваться, если демон был остановлен как-нибудь грубо
  • nodaemon - в основном тоже, что и start (в том числе происходит смена прав и т.п.), но при этом процесс не делает себе fork и не отключается от терминала, что даёт возможность наблюдать весь его вывод - удобно для отладки демона.
Файл конфигурации демона может содержать определение нескольких переменных, влияющих на поведение демона, вот полный конфиг моего домашнего демона:
(defparameter *name* "homesite")

(defparameter *user* "andrey")

(defparameter *swankport* 9000)

(defparameter *asdf-central-registry*
'(#P"/usr/share/common-lisp/systems/" #P"/home/andrey/development/common-lisp/common-lisp-systems/"))

(defparameter *asdf-load-systems* '(#:homesite))

(defparameter *sites* '((#:homesite nil 80)))
Возможны следующие параметры:
  • *name* - имя демона, в данный момент единственный обязательный параметр
  • *user* - имя пользователя, от которого должен работать демон, по-умолчанию совпадает с именем демона
  • *group* - группа пользователя
  • *fasldir* - путь к каталогу, в который будут складываться fasl-файлы, по-умолчанию определяется из имени демона как (format nil "/var/cache/~A/fasl/" *name*)
  • *pidfile* - pid-файла демона, по-умолчанию (format nil "/var/run/~A/~A.pid" *name* *name*)
  • *swankport* - порт, на котором следует запускать swank, если не указан или nil, то swank не запускается
  • *default-host-redirect* - в RESTAS сайты запускаются с помощью restas:start-site, при этом можно указать имя хоста (аналог виртуальных хостов), данная переменная указывает хост, на который будет производиться редирект, если для хоста запроса нет сайта, например, на lisper.ru можно попасть набрав в браузере www.lisper.ru, lisp.catap.ru или просто прямой адрес, все такие запросы перенаплавляются на lisper.ru
  • *asdf-central-registry* - список директорий, в которых будет производиться поиск систем
  • *asdf-load-systems* - список систем, которые необходимо загрузить при старте демона
  • *sites* - список сайтов, которые необходимо запустить при запуске демона с помощью функции restas:start-site. В качестве сайта может быть указано просто имя, либо список, например '(#:rulisp "lisper.ru" 80). Сайты определяются с помощью restas:defsite - это происходит при загрузке указанных asdf-систем.
Для систем на базе Gentoo есть специальная поддержка. При установке restas с помощью моего форка gentoo-lisp-overlay в каталог /etc/init.d/ добавляется скрипт restas.lo. Теперь, для создания initd-скрипта необходимо создать симлинк на этот файл и создать конфигурационный файл в /etc/conf.d/, например:
cd /etc/init.d/
ln -s restas.lo homesite
emacs /etc/conf.d/homesite.conf
/etc/init.d/homesite start
Схема может быть ещё не совсем устоявшаяся, но уже рабочая :)

пятница, 11 декабря 2009 г.

archimag/gentoo-lisp-overlay

Немного поразмышляв решил форкнуть gentoo-lisp-overlay, в конце концов, ведь именно для этого и придумали git :) Итак, http://github.com/archimag/gentoo-lisp-overlay, помимо прочего содержит мои пакеты:
Кроме того, он также содержит cl-typesetting и мой форк cl-pdf, который включает в себя поддержку salza2, zpb-ttf и некоторые мои изменения.

При использовании layman проще всего подключить этот форк вместо оригинальной версии следующим образом:
cd /usr/local/portage/layman/lisp
git remote rm origin
git remote add origin git://github.com/archimag/gentoo-lisp-overlay.git
git config branch.master.remote origin
git config branch.master.merge master
(Пишу по памяти, могу немного напутать)
Теперь
layman --sync lisp
Будет брать обновления из моего репозитория, который я обещаю регулярно синхронизировать с основным.

Открыт приём багов и предложений :)

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

restas-wiki

Закончил отделение исходного кода wiki, используемой на lisper.ru, от собственно исходного кода lisper.ru и оформил это дело в виде отдельного restas-плагина - restas-wiki. Теперь, весь код для поддержки wiki на lisper.ru выглядит следующим образом:
(restas:define-site-plugin rulisp-wiki (#:restas.wiki)
(restas.wiki:*baseurl* '("wiki"))
(restas.wiki:*wiki-dir* #P"/var/rulisp/wiki/")
(restas.wiki:*wiki-user-function* #'compute-user-login-name)
(restas.wiki:*finalize-page* #'(lambda (content)
(rulisp.view.fine:main-frame (list :title (getf content :title)
:css (css-files-data '("style.css" "wiki.css"))
:user (compute-user-login-name)
:main-menu (main-menu-data)
:content (getf content :content)
:callback (hunchentoot:request-uri*))))))

пятница, 4 декабря 2009 г.

Common Lisp и WEB

Я хотел бы озвучить тезис, который для меня является совершенно очевидным, но который, как мне кажется, не находит широко понимания среди людей, интересующихся Common Lisp. Итак:
Наиболее важная и перспективная область применения Common Lisp это web-технологии
Текущая ситуация в IT такова, что язык, не пригодный для web-разработки, обречён на прозябание и наоборот, язык, цветущий в мире web, будет находить всё новые и новые области применения. Тут хотелось бы заострить внимание на двух мифах.

Миф 1
Есть мнение, что изучение некоторых языков (к коим часть говорящих относит и Common Lisp) является очень важным для развития мышления и т.п. Т.е. нет необходимости и не очень много смысла писать на этих языках реальный софт, достаточно просто уделять их изучению некоторое время. Это можно сравнить с посещением спортивного зала: посидел пару часов над XXX, прокачал мозг, и можешь снова возвращаться в реальный мир Java/C#/т.п. где без сомнения будешь с лёгкость превосходить всех прочих...

Я думаю, что это полный бред. Язык это всего лишь инструмент. Великий скульптор создаст шедевр вооружившись одним только каменным топором, я же, при всём богатстве современных инструментов, не смогу создать даже подобия скульптуры. Ни в одной другой области человеческой деятельности мне не доводилось слышать мнения о принципиальной важности изучения инструментов для развития способностей в этой области. Зато, практически во всех областях (с которыми я сталкивался) отмечается важность изучения предшествующего опыта. И вот загвоздка: среди образцов кода и идей, которые производили на меня впечатление, чаще всего втречается C и PHP. Зачем изучать Lisp, Haskell и т.п., если наиболее ценные образцы кода написаны на языках, которые и изучать то особо не надо (настолько они просты) ...

Миф 2
Ещё говорят, что у каждого языка есть своя область применений и при построении реальных больших систем надо использовать разные языки для разных компонентов, а Common Lisp - язык для программирования искусственного интеллекта. Т.е. например пишем на CL какой-нибудь очень интеллектуальный модуль, а веб-морду к нему делаем на PHP. Ну и т.п.

Я абсолютно уверен, что ни один из утверждающих подобное не пробовал применить свой рецепт на практике. Я вот попробовал, и проблемы взаимодействия, проблемы разделения логики и сопутствующие проблемы дублирования кода однозначно привели меня к мысли, что универсальный язык в проекте должен быть только один. Попытки лёгкой интеграции за счёт использования технологий, подобных COM, .Net, JVM и т.п. порождают проблемы, в значительной степени обесценивающие получаемые преимущества. Да, есть DSL, такие как XSLT, которые восхитительно хороши для решения некоторых частных задач, но смешивать в одном проекте, например, Python, Perl и Common Lisp по крайней мере не разумно.

Повторюсь, что я имел опыт подобного смешивания, я использовал Common Lisp для самой интеллектуальной части системы и результатом этого явилось понимание, что если я хочу использовать Common Lisp, то я должен быть готов использовать его для построения всей системы, а не только одного компонента.

Что мы имеем сейчас
Человек, "реально" (ну т.е. примерно как я :)) смотрящий на вещи и заинтересовавшийся Common Lisp найдёт очень удручающую картину. Использовать CL для написания небольших скриптов и десктопных приложений фактически нельзя, что связано с временем запуска, требования к памяти и размеров генерируемых образов. Ну т.е. теоретически можно, но расчитывать на широкое использование CL на десктопе несколько наивно, он (CL) создавался явно не для этого. Да и вообще, декстоп это не очень интересно. Итак, остаётся веб. Вот тут картина так просто ужасающая, пройдитесь по основным ресурсам, например
Что мы там найдём? Жуткий интерфейс примитивной вики, apache 1.3 и cgi-скрипты, PHP. Картина маслом. Для усиления эффекта можно сразу после этого зайти, например, на jboss.org и сделать какой-нибудь недвусмысленный вывод. Не очень впечатлительные люди могу проигнорировать это и попробовать разобраться с имеющимися библиотеками и это достойно отдельного обсуждения :) Например, если ранее вы писали на C++ и таки не смогли "отстрелить себе ногу", то у вас есть реальный шанс отстрелить себе сразу голову с помощью Weblocks или UCW. Или, если вы настольгируте по cgi-скриптам на Perl, то наверняка вам придётся по вкусу единственная (!) известная библиотека шаблонов html-template. Ну и т.д.

На днях появился пост Why I chose Common Lisp over Python, Ruby, and Clojure - я просто плакал, на фоне жалкой аргументации больше всего запоминается признание того факта, что сделать каркас с помощью RoR или Django было бы намного быстрее.

И, кстати, для любителей творчества Пола Грэма, будет небезынтересно узнать, что Viaweb был переписан на Perl и С++ почти сразу после его ухода :)

Удивительное рядом
Больше всего меня поражает, что под кучей хлама скрывается суть, сам Common Lisp - удивительно мощный и яркий язык. И для него существую такие блестящие реализации, как SBCL. Конечно, это возможно вопреки текущему уровню интереса только благодаря 50 годам непрерывного развития. В последнее время ситуация стала несколько изменяться, стали появляться качественные современные библиотеки, например, ironclad или iolib. И мне кажется, что этот накопившийся хлам можно расчистить. Но для этого нужен прорыв, и этот прорыв должен быть сделан в области web, ибо достижения в других областях никто особо не оценит.

Так вот, я предлагаю вместо бесконечных споров, которые можно наблюдать в интернете, по поводу "мифических свойств" CL, и которые отбирают часы времени только на прочтение, - писать код, писать реальный код, писать реальный код для веб, и да, пусть это будет open source. Если вы хотите способствовать развитию и популяризации Common Lisp, то вы можете сделать это только через реальные проекты, ибо только код имеет ценность. Ну и конечно, надо избавляться от PHP и прочих подобных технологий на ресурсах, связанных с Common Lisp - любой должен быть в состоянии убедиться, что о CL можно не только рассуждать, но и писать на нём реальные системы.

P.S. Если ранее вы не занимались разработкой на Common Lisp для веб, но хотите попробовать - обращайтесь (можно здесь, но лучше на lisper.ru) - буду рад помочь.

среда, 2 декабря 2009 г.

closure-template-html-mode

На ELisp никогда не писал, но поскольку начал активно использовать cl-closure-template в своих проектах, то встала проблема редактирования файлов шаблонов и пришлось таки что-то придумывать, ибо редактировать как простой текст, мягко говоря, не очень удобно. По-началу использовал html-mode, но всё равно не то. Итак, мой первый опыт программирования на ELisp - closure-template-html-mode. Режим основа на sgml-mode и, пока, добавляет только подсветку синтаксиса шаблонов (с небольшими помарками), что, в принципе, тоже неплохо. Для полного (моего) счастья надо написать closure-template-indet-line, но для этого надо разобраться с работой sgml-indent-line, которая выглядит несколько устрашающе. Буду премного благодарен, если кто-нибудь сможет помочь с написание данной функции.

вторник, 1 декабря 2009 г.

restas-planet

Полностью отделил код планеты, используемой на lisper.ru, от кода rulisp, теперь это совершенно независимый restas-плагин. Его можно использовать для создания планеты как независимого приложения, так и в рамках какого-либо сайта. Вот простейший кода, использующий этот плагин:
(asdf:operate 'asdf:load-op '#:restas-planet)

(restas:defsite #:myplanet
(:use #:cl))

(in-package #:myplanet)

(restas:define-site-plugin planet (#:restas.planet)
(restas.planet:*feeds* #P"/path/to/feeds/description")
(restas.planet:*name* "My Planet"))

(restas:start-site '#:myplanet :port 8080)
В качестве значения restas.planet:*feeds* должен использоваться путь к файлу, подобному следующему:
(feed "http://swizard.livejournal.com/data/atom" :category "lisp")
(feed "http://lisp.tomsk.ru/wordpress/feed/")
Для настройки плагина можно использовать следующие параметры (все из пакета #:resas.planet):
  • *baseurl* - базовый url
  • *feeds* - путь к файлу с описаниями лент (см. выше)
  • *name* - имя планеты, по-умолчанию "PLANET"
  • *template* - функция генерирующая html-код, по умолчанию - 'restas.planet.view:feed-html
  • *schedule* - расписание обновления лент в формате библиотеки clon, по-умолчанию - '(:hour *) (обновление раз в час)
  • *cache-dir* - каталог для кэша, в том числе, кэша лент. Если не указан - кэш не используется.
Исходный код плагина доступен здесь: http://github.com/archimag/restas-planet
Патчи приветствуются (как и просто желающие принять участие в разработке).

вторник, 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 практичность, как таковая, совершенно отсутствует, очень сильно настораживает...

среда, 28 октября 2009 г.

Hunchentoot, Apache и имя хоста

При использовании hunchentoot в связке с Apache (или там nginx), есть небольшая проблема: метод hunchentoot:host возвращает имя хоста вместе с реальным портом, на котором работает hunchentoot. Проблема возникает из-за того, что это имя может использоваться при генерации url или для операций hunchentoot:redirect. На самом деле, этот порт, на котором работает hunchentoot, совершенно не интересен, мне бы хотелось, что бы метод hunchentoot:host возращал имя хоста на котором работает Apache (nginx), т.е. то имя, которое видят пользователи. 30 секундное изучение исходников hunchentoot подсказывает простое решение:
(defmethod hunchentoot:header-in ((name (eql :host)) request)
(or (hunchentoot:header-in :x-forwarded-host request)
(call-next-method)))
Всё, теперь hunchentoot:host всегда будет возвращать "правильное" значение, независимо от того, работает ли hunchentoot в связке с Apache (nginx) или же самостоятельно.

среда, 21 октября 2009 г.

Маршруты в RESTAS

Routes является ключевой концепцией RESTAS, собственно, с них и началась разработка системы. Первоначально, идею routes, в более упрощённом виде, я придумал самостоятельно, когда разрабатывал REST-службу для одного приложения. Это был мой первый опыт web-разработки и я ничего не знал о других системах (моё приложение изначально было реализовано на C++ как модуль для веб-сервера Apache, а в последующем переписано на Python под mod_python). По мере моего знакомства в веб-технологиями я узнал, что "придуманная" мной система диспетчеризации называется routes и, насколько я понимаю, впервые появилась в Ruby On Rails. Если вы не знакомы с данной концепцией, то могу порекомендовать следующее описание (Python-реализация): http://routes.groovie.org/manual.html. Другой, концептуально очень близкой системой, является URLConf в Django.

Когда я стал использовать Common Lisp для разработки web-приложений, то больше всего меня расстраивала система диспетчеризации запросов web-сервер Hunchentoot, точнее фактическое её отсутствие (а входящий в поставку define-easy-handler просто ужасен). Для решения данной проблемы я решил написать cl-routes: реализацию системы маршрутов для Common Lisp, но без привязки к модели MVC. Первоначально я планировал портировать Python-реализацию этой идеи на Common Lisp и принялся изучать её исходный код. Первое, что меня смутило - размер исходного кода, слишком много букв... А второе меня так просто добило: все маршруты транслируются в регулярные выражения и при обработке запроса идёт простой перебор всех выражений до первого совпадения. Брр... Фактически, системы маршрутизации RoR и Django работают точно так же, поиск ведётся прямым перебором. В документации к Django написано про производительность буквально следующее:
Each regular expression in a urlpatterns is compiled the first time it's accessed. This makes the system blazingly fast.
ага, как же, если количество маршрутов будет исчисляться сотнями или даже тысячами (что вполне реалистично для больших приложений), то такая реализация может легко стать главным тормозом всего приложения. И между прочим эта проблема достаточно хорошо известна в RoR, где предлагаются различные пути для увеличения производительности готовых приложений за счёт отказа от многих преимуществ маршрутов и увеличения нагрузки на разработчиков. В Django, в общем, есть кое-какие пути для увеличения производительности (за счёт задания префиксов на группы) (я правда не знаю, повышает ли это реально производительность или нет, но принципиально может), но это опять таки ведёт к увеличении нагрузки на программиста и не всегда может быть применено.

В общем, к существующим системам у меня есть две основные претензии:
  • Использование универсального и тяжеловесного механизма регулярных выражений, который просто не нужен для разбора url
  • Поиск в линейном списке (с некоторыми оговорками для Python-систем) - ведь структура сайта обычно естественным образом отображается в виде дерева, а значит возможен и более эффективен (чем больше сайт, тем более эффективен) поиск в дереве
cl-routes не используют регулярных выражений (вместо этого используется механизм унификации), и компилирует все маршруты в одно дерево, что предоставляет возможность более эффективного поиска. Описание первоначальной идеи реализации можно посмотреть в моём старом сообщении - она с тех пор не очень сильно изменилась. При задании маршрутов всегда возможны конфликты, когда один и тот же url соответствует нескольким шаблонам. В системах с последовательным поиском подходящего маршрута всегда выбирается первый, cl-routes пытается выбрать наиболее специальный, например тот, в котором больше переменных, или который имеет более длинную статическую часть. Точного описания алгоритма разрешения коллизий пока дать не могу, но у меня до сих не было с этим никаких проблем, поведение системы вполне разумно и по большей части соответствует интуитивным ожиданиям.

Кроме того, что в RoR, что в Django для внесения изменений в схему диспетчеризации необходимо внести изменения в несколько файлов, я же предпочитаю держать определение схемы url и её обработчик в одном месте, не размазывая логику по разным частям программы. В RESTAS задать новый маршрут можно так:
(restas:define-route article ("articles/:author/:item"
:method :get
:content-type "text/plain")
(format nil
"Author: ~A~Article: ~A"
author
item))
Т.е. в одном месте задаётся и шаблон url, и обработчик, при этом в теле обработчик сразу же доступны переменные, заданные в шаблоне url. Вложенный обработчик, по-умолчанию, может вернуть строку или "octets array", либо целое число (которое интерпретируется как код статуса ответа), либо pathname (в этом случае вызывается hunchentoot:handle-static-file, которая, в отличие от систем на базе Ruby или Python работает достаточно быстро и реальной необходимости в дополнительных серверах для "статики", таких как ngix, просто нет). Также, легко можно добавить поддержку и обработку любых типов возвращаемых объектов.

При определении маршрута через define-route необходимо указать символ (в приведённом примере - 'article), который будет являться его именем. Во-первых, это даёт возможность в любой момент переопределить маршрут (в том числе и шаблон url, лишь бы имя оставалось неизменным) простой отправкой кода в REPL (например, с помощью M-C-x в SLIME). Во-вторых, это позволяет использовать данный символ для генерации url:
(restas:genurl 'article
:author "archimag"
:item "introduction-in-routes")
или для перенаправления:
(restas:redirect 'article
:author "archimag"
:item "introduction-in-routes")
(возможны и другие использования)

В Django шаблон url задаётся регулярным выражением и возможности сопоставить одному и тому же регулярному выражению несколько различных обработчиков (например, для различных типов запроса: get, post и т.п.) нет, в cl-routes маршрут это объект класса (либо производный от него) routes:route, для которого можно переопределить метод route-check-conditions, что позволяет проводить диспетчеризацию не только на основе шаблона url. Это используется в RESTAS и позволяет одному и тому же шаблону url сопоставить несколько обработчиков, например, для разных типов (get, post и т.п.) запросов (что позволяет сократить количество if-лапши в коде). В ближайшее время (на днях, может даже сегодня) я добавлю в RESTAS возможность проводить произвольные проверки во время поиска подходящего обработчика: например сейчас у меня есть необходимость в разных обработчиках для одного и того же url в зависимости от роли пользователя в системе.

Вот несколько возможных шаблонов url:
/forum/:chapter/:topic/:message
/forum/archives/:year/:month/:day
/forum/:chapter/rss
Или с объединением строк:
/book/:(name)-:(chapter).html
/:(feed).:(format)
Также поддерживаются wildcard-параметры (с тем ограничением, что такой параметр может использоваться в шаблоне только один раз):
/mydoc/*items
/*path/rss

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

RESTAS - платформа для разработки web-приложений на Common Lisp

Я сделал первый релиз RESTAS-0.0.1 - платформы для разработки веб-приложений на Common Lisp. На данной платформе сейчас работает ресурс lisper.ru, а также я использую её в своих рабочих проектах. Я начал разрабатывать RESTAS чуть менее года назад, поскольку существующие для CL фрэймворки меня совершенно не устраивали. Понаписал сначала кучу всякой ерунды и потом стал обрубать ненужное и выделять главное, пару раз полностью переписывал, так что последние полгода размер исходного кода только уменьшался. Изначально проект зависел от другой моей библиотеки - cl-libxml2, но эта зависимость была удалена и сейчас RESTAS акцентирует внимание на трёх основных моментах:
  • Диспетчеризация на основе системы маршрутов, аналогичных системе маршрутов Ruby On Rails, но без привязки к модели MVC.
  • Механизме повторного использования кода - системе плагинов. По задумке, она должна позволить разрабатывать веб-приложения, такие как форум, вики и т.п. и в последующем легко встраивать их в сайты на базе RESTAS. Проблема достаточно сложная, но сейчас я вижу хорошие перспективы для её приемлемого и довольно простого решения.
  • Интерактивной разработке и горячей замене кода. Любой код, относящийся к RESTAS (такой, как определение маршрута или плагина) может быть в любой момент "перекомпилирован" (например, с помощью C-M-x в SLIME) и внесённые изменения можно немедленно увидеть в браузере. Никаких перезагрузок веб-сервера и т.п. сложных действий.
Документации пока нет и единственное, что я могу сейчас предложить, помимо исходного кода lisper.ru (http://github.com/archimag/rulisp), это примеры двух простейших демонстрационных приложений и одного чуть более полезного плагина (который, будет развиваться):
В данный момент проект имеет следующие зависимости:
и для имеющих настроенную CL-среду его установка не должна вызвать каких-либо сложностей

Я приглашаю всех интересующихся веб-разработкой на языке Common Lisp принять участие в развитии этого проекта. Специально для обсуждения связанных с этим вопросов создал отдельный форум.

Ссылки:
Страница проекта: http://code.google.com/p/restas/ (пока там ничего нет)
Обсуждение: http://lisper.ru/forum/restas
Исходный код: http://github.com/archimag/restas
Скачать архив: http://restas.googlecode.com/files/restas-0.0.1.tar.bz2

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

cl-mssql

По работе мне необходимо вычитывать данные из 1С, поэтому возникла необходимость в разработке библиотеки для работы с MS SQL Server. SQL Server и Sybase поддерживают протокол TDS (Tabular Data Stream), для которого есть свободная реализация FreeTDS. Я, признаться, совершенно туп в этом вопросе, поэтому взял код pymssql и путем его анализа написал на Common Lisp пару сотен строк, которые используют libsybdb.so (cffi) и позволяют:
  • соединяться с базой
  • делать запрос и получать результат (далеко не для всех типов данных)
  • конвертировать ошибки и предупрежедния сервера в conditions и warrnings
В данный момент ничего больше от этой библиотеки мне не надо и развития она пока не получает, а жалко, может кому может пригодиться. Поэтому, приглашаются желающие помочь развитию библиотеки :)

Страница проекта: http://code.google.com/p/cl-mssql/
Исходный код: http://github.com/archimag/cl-mssql

Да, тут есть один важный момент. Часто в MS SQL данные хранятся в cp1251 (особенно, ели речь идёт о 1С), но babel (от которой зависит CFFI) версии 3.0 эту кодировку не поддерживает. Для решения этой проблемы я написал патч, который сейчас включён в исходный код проекта, но релиза с тех пор ещё не было. Так что, если вы захотите использовать cl-mssql с cp1251 необходимо использовать последнию версию babel:
darcs get http://common-lisp.net/project/babel/darcs/babel

четверг, 1 октября 2009 г.

Что я думаю о ФП

Наблюдая очередной срач на LOR-е, посвящённый выходу второго номера журнала "Практика функционального программирования" не перестаю удивляться. Дело дошло до обсуждения идиотского примера приготовления яичницы: как лучше описать решение? В императивном или функциональном стиле? Или может ещё надо добавить немного ООП?

Я не знаю... А кто это может знать? Приступая к разработке какого-либо сложного "куска кода" я слишком мало знаю о нём, что бы обоснованно судить какая парадигма лучше всего подходит. А пишу криво и много выкидываю, затем переделываю, снова и снова. И вот, после многократного рефакторинга, после многих дней или недель работы над проблемой, когда я уже знаю очень много и о проблеме и об коде, который написал, у меня может появиться мнение о предпочтительной парадигме, не в результате предварительного анализа, но в результате естественного развития кода.

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

Common Lisp удивительно хорош не только тем, что сочетает в себе различные парадигмы, что даёт возможность осуществить смену парадигмы для конкретного куска кода без смены языка, но и тем, что значительная часть языка посвящена облегчению процесса программирования (хакерства - в терминологии Пола Грэма), не облегчения кодирования известного алгоритма (хотя и в этом с ним мало кто может соперничать), но прежде всего поддержке процесса поиска решения. И благодаря именно этим возможностям возможен такой мощный инструмент, как SLIME. Пара свежих примеров из собственной практики:
  • Порой хочется пропатчить какую-нибудь библиотеку, что бы изменить поведение какой-либо функции. Просто для проверки, как изменится поведение моего кода. Либо порой может оказаться полезным добавить в библиотечную функцию диагностическое сообщение. При использование многих других языков это довольно проблематично, надо сделать очень много действий и обычно, без крайней необходимости, лучше такого не делать. В Common Lisp при использование SLIME это настолько тривиально, что я пользуюсь этим приёмом постоянно, надо то: установить курсор в месте вызова функции, нажать C-M-. - в результате откроется код нужной функции, с помощью C-x C-q переключить флаг read-only (это даст возможность редактировать буфер, но не сохранить на диск), исправить код и теперь С-M-x перекомпилирует изменённую функцию, всё, можно смотреть как изменилось поведение программы

  • Сайт lisper.ru иногда зависает. Это случается достаточно редко и у меня до сих не было возможности диагностировать ошибку. Но вот вчера сайт снова завис, а я имел немного свободного времени, что бы разобраться. Выяснение точного места в коде, приводящего к этому заняло менее 5 минут: с помощью SLIME подключился к серверу (который находится в другой стране), получил список потоков, выбрал поток "Hunchentoot listener" и выполнил команду slime-thread-debug, что привело к выводу стека вызовов зависшего потока, я увидел необработанное исключение и в течении минуты установил точное место в коде hunchentoot, где оно возникает и не получает должной обработки (надо заметить, это довольно редкая проблема, по крайней мере, я сталкиваюсь с ней только на одном сервере и то, иногда).
Может быть программы на ФП-языках действительно занимают меньшее количество строк кода и их проще понимать (при должной сноровке), чем программы на иных языках, хотя мне совершенно не понятно на чём основано подобное мнение (если проводить сравнение ФП-языков с таким языками как Common Lisp или Python; понятно, что трудно выглядеть проигрышно по сравнение с С). Но какое это имеет значения? Если важна на самом деле не красота/лаконичность законченного решения, а сложности пути, которым оно было достигнуто... ИМХО, сторонники ФП слишком большой упор делают на фукнциональную парадигму, на рассказы о том, насколько хорош язык для кодирования заранее известных алгоритмов (может быть это и так, но мне не очень это интересно), но вопрос применимости языков для реальной разработки остаётся соверешенно открытым. Можно ли пользоваться ФП без боли?

В общем, я хорошего мнение о функциональной парадигме как таковой, но совершенно против языков подобных Haskell.

пятница, 25 сентября 2009 г.

Перевод Practical Common Lisp в формате PDF

Перевод Practical Common Lisp теперь доступен в формате PDF: http://lisper.ru/pcl/pcl.pdf. Ещё не всё реализовано, в частности, не показываются таблицы, но читать можно :) Вся работа по генерации PDF выполняется pure-lisp кодом, каждый час, после синхронизации с основной вики.

среда, 23 сентября 2009 г.

Переменные, локальные относительно контекста

Gnu Emacs поддерживает Buffer-Local Variables, т.е. переменные, которые являются локальными относительного конкретного буфера, а в документации также указано, что в будущем возможно появление переменных, локальных относительно фрэйма или окна. Т.е. вырисовывается достаточно чёткая концепция переменных, локальных относительно некоторого контекста. Это находит применение в Gnu Emacs, но может оказаться полезным и для решения других задач, по крайней мере, я обратил внимание на Buffer-Local Variables уже после того, как сам пришёл к подобной идеи в процессе размышлений над структурой сервера приложений.

Реализация этой идеи на Common Lisp весьма тривиальна:

(defun make-preserve-context ()
(make-hash-table))

(defun context-add-variable (context symbol)
(setf (gethash symbol context)
(symbol-value symbol)))

(defun context-remove-variable (context symbol)
(remhash symbol context))

(defun context-symbol-value (context symbol)
(gethash symbol context))

(defun (setf context-symbol-value) (newval context symbol)
(setf (gethash symbol context)
newval))

(defmacro with-context (context &body body)
`(let ((cntx ,context))
(if cntx
(let ((symbols)
(values))
(iter (for (s v) in-hashtable cntx)
(push s symbols)
(push v values))
(progv symbols values
(unwind-protect
(progn ,@body)
(iter (for s in symbols)
(setf (gethash s cntx)
(symbol-value s))))))
(progn ,@body))))


Теперь можно создать "защищённый контекст", добавить в него несколько ранее объявленных глобальных переменных и изолировать с помощью макроса with-context код, оперирующий этими переменными, от остального мира.

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

ClozureCL и lisper.ru

Немного подправил код lisper.ru и смог запустить его на Clozure CL. Радость была, однако, недолгой... Попробовал провести нагрузочное тестирование с помощью ab, но оказалось, что если указывать для ab параметр -c больше 1 (в этом случае тестирование ведётся в несколько потоков), то hunchentoot валится без вариантов. При тестировании в одном потоке (-с 1) SBCL отдаёт примерно в 8 раз больше запросов в секунду, чем Clozure CL. Печально...

воскресенье, 20 сентября 2009 г.

Common Lisp Daemon. Часть 2.

В предыдущей части я рассказал о преодолении основных технических трудностей, возникающих при создании lisp-демонов. Теперь я готов опубликовать код законченного lisp-демона. Полный код можно посмотреть здесь: http://lisper.ru/apps/format/15. Это реальный код, который используется для запуска сайта lisper.ru. Как мне кажется, код достаточно тривиален и по структуре в основном соответствует структуре демонов, написанных на языке C (информацию о принципах разработки демонов на языке С легко найти в сети). Отдельно хотел бы отметить, что данный демон позволяет запускать hunchentoot на 80-ом порту уже после отказа от root-привилегий, что достигается за счёт использования библиотеки libcap (версии 2). При использования данного демона нет необходимости в вспомогательных средствах: GNU Screen, detachtty и т.п. (которые традиционно рекомендуются для решения данной проблемы). Ну и данный код, в том числе, можно рассматривать как пример системного программирования на Common Lisp :) К сожалению, в код пришлось добавить пару платформо-зависимых констант, но я надеюсь при случае написать патч для SBCL, после чего необходимость в этом отпадёт.

Изменив всего несколько строк можно приспособить данный код для запуска другого демона. Но подобная техника, конечно, не есть "хорошо", поэтому я сейчас раздумываю об написании библиотеки, которая позволит создавать демонов в несколько строк кода.

среда, 16 сентября 2009 г.

Common Lisp Daemon. Часть 1.

При развертывании Common Lisp приложения (например, сайта) на сервере (естественно, под управление GNU/Linux) возникает проблема организации lisp-демона, что связано с некоторыми техническими проблемами, которые, вероятно, могут вызвать у начинающих некоторое непонимание (или даже ужас).

Корень этих проблем в том, что обычно lisp процесс не может быть оторван от терминала (причины этого восходят к тем далёким временам, когда компьютеры были большими...) и при закрытии стандартного потока ввода немедленно завершает свою работу.

Для решения этой проблемы наибольшую известность получило решение на базе GNU Screen. Менее известен, но также используется метод основанный на использовании detachtty.

Я пробовал оба этих варианта и совершенно не удовлетворён ни одним из них (хотя и должен заметить, что решение на базе GNU Screen значительно удобней). В конце концов, это просто не нормально, что для решения проблемы демонизации необходимо использовать "не-lisp" инструменты, а итоговое решение очень сильно напоминает хак. С другой стороны, почему бы не реализовать демонизацию средствами самого Common Lisp, что здесь невозможного? Если исходить только из стандарта Common Lisp, то, очевидно, задача действительно нерешаема (ибо разработчики этого самого стандарта кажется совершенно не размышляли на данную тему), но если взять конкретную реализацию? Я попробовал реализовать pure-lisp демонизацию для SBCL и выяснил, что это довольно тривиально.

Итак, что должен делать приличный демон? Обычно это:
  • Отсоединиться от создавшего его родительского процесса (сделать fork)
  • Снизить свои привилегии до минимально возможных (ведь обычно демоны запускаются от имени root)
  • Закрыть стандартные потоки ввода/вывода/ошибок (0, 1, 2)
  • Сигнализировать об успешном запуске (либо сообщить об ошибке)
Все эти действия могут быть реализованы тривиальным образом.

Отсоединение от родительского процесса

Стандартно это делается за счёт системного вызова fork и ничего сложного из себя не представляет. Тут даже и говорить особо нечем:
(unless (= (sb-posix:fork) 0)
(quit))
Реальный код, конечно, будет немного сложнее, но так тоже работает.

Снижение привилегий

Работать от root демону совершенно ни к чему, поэтому:
(defun change-user (name &optional group)
(let ((gid)
(uid))
(when group
(setf gid
(sb-posix:group-gid (sb-posix:getgrnam group))))
(let ((passwd (sb-posix:getpwnam name)))
(unless group
(setf gid
(sb-posix:passwd-gid passwd))
(setf uid
(sb-posix:passwd-uid passwd))))
(sb-posix:setgid gid)
(alien-funcall (extern-alien "initgroups" (function int c-string int)) name gid)
(sb-posix:setuid uid)))

;;; устанавливаем владельцем процесса пользователя rulisp
(change-user "rulisp")
Данный код, правда, содержит очевидный изъян (если вспомнить о saved user id), но для демонстрации принципиальной возможности это не критично.

Переключение на псевдо-терминал

Просто так закрыть стандартный ввод нельзя (это приведёт к завершению процесса), но его можно переключить на псевдо-терминал. При использовании GNU Screen происходит переключение (на псевдо-терминал) всех стандартных потоков, но я использую GNU Screen только для того, чтобы увидеть какие ошибки произошли при запуске демона, т.е. полный сервис, предоставляемый GNU Screen является совершенно избыточным. Поэтому, я считаю достаточным связать стандартный поток ввода с псевдо-терминалом (для предотвращения угрозы завершения процесса, после чего о нём вообще можно забыть), а стандартные потоки вывода и ошибок просто направить в файл (что даст возможность быстро просмотреть как происходила загрузка демона):
(defun switch-to-slave-pseudo-terminal (out)
(flet ((c-bit-or (&rest args)
(reduce #'(lambda (x y) (boole boole-ior x y))
args)))
(let* ((fdm (sb-posix:open #P"/dev/ptmx" sb-posix:O-RDWR))
(slavename (progn
(alien-funcall (extern-alien "grantpt" (function int int)) fdm)
(alien-funcall (extern-alien "unlockpt" (function int int)) fdm)
(alien-funcall (extern-alien "ptsname"
(function c-string int)) fdm)))
(fds (sb-posix:open slavename sb-posix:O-RDONLY))
(log (sb-posix:open out
(c-bit-or sb-posix:O-WRONLY sb-posix:O-CREAT sb-posix:O-TRUNC)
(c-bit-or sb-posix:S-IREAD sb-posix:S-IWRITE sb-posix:S-IROTH))))
(sb-posix:dup2 fds 0)
(sb-posix:dup2 log 1)
(sb-posix:dup2 log 2))))

;;;; переключаем стандартный поток ввода на пседо-терминал,
;;;; а потоки вывода и ошибок направляем в /tmp/log
(switch-to-slave-pseudo-terminal #P"/tmp/log")
До сих пор мне не приходилось работать с псевдотерминалом, так что мог что-нибудь сделать не вполне корректно, но на моей системе работает без проблем.

Сигнализация об успешном запуске демона

После вызова fork получается два процесса, один из которых становится демоном, а другой завершается и, вообще говоря, должен сигнализировать об успешности запуска демона. При использовании решения на основе GNU Screen такого эффекта добиться не получается: узнать об успешности запуска демона можно только по результатам его работы. Для решения этой проблемы можно использовать демона с обратной связью, что требует возможности обработки сигналов. Послать сигнал родительскому приложению очень просто:
(sb-posix:kill (sb-posix:getppid) sb-posix:sigusr1)
Для обработки сигналов в SBCL можно воспользоваться функцией sb-sys:enable-interrupt:
(sb-sys:enable-interrupt sb-posix:sigusr1 #'usr1-signale-handler)
Итого:
Все технические проблемы, возникающие при создании демона, для SBCL решаются довольно тривиальным образом и необходимости в использовании GNU Screen и т.п. средств нет.

P.S. Это были технические детали, а в следующей части я покажу законченное решение, использующее описанные возможности (а также кое-что ещё) для запуска реального "Common Lisp Daemon".

пятница, 28 августа 2009 г.

Лисперу cron не нужен!

Для возможности отображения перевода PCL на lisper.ru необходимо регулярно скачивать и распаковывать свежий snapshot. Обычный способ - поместить соответствующее задание в cron. Однако, если вы пишете на Common Lisp, то можете легко реализовать pure-лисп решение, и, при этом, получить чуть больший уровень гибкости.

Для решения этой задачи потребуются следующие пакеты (благо, они уже установленные на lisper.ru)
Код тривиален:
;;;; Некоторые настройки
(defparameter *pcl-snapshot-url* #u"http://pcl.catap.ru/snapshot.zip")
(defparameter *pcl-snapshot-dir* #P"/var/rulisp/pcl/")
(defparameter *pcl-load-snapshot-p* t)

;;;;; Функция для загрузки и разархивирования
(defun load-pcl-snapshot ()
(when *pcl-load-snapshot-p*
(let ((snapshot-path (ensure-directories-exist (merge-pathnames (car (last (puri:uri-parsed-path *pcl-snapshot-url*)))
*pcl-snapshot-dir*)))
(snapshot (drakma:http-request *pcl-snapshot-url*
:force-binary t)))
(when snapshot
(with-open-file (out
snapshot-path
:direction :output
:element-type '(unsigned-byte 8)
:if-exists :supersede)
(write-sequence snapshot out))
(zip:unzip snapshot-path
*pcl-snapshot-dir*
:if-exists :supersede)
(setf *pcl-dir*
(merge-pathnames "var/www/pcl.catap.ru/htdocs/data/pages/pcl/"
*pcl-snapshot-dir*))
t))))

;;;; Выполняем (load-pcl-snapshot) по расписанию, скажем, один раз в час
(if *pcl-load-snapshot-p*
(clon:schedule-function 'load-pcl-snapshot
(clon:make-scheduler (clon:make-typed-cron-schedule :hour '*)
:allow-now-p t)
:thread t))

Wiki на lisper.ru

В общем, наш футбол меня вчера настолько расстроил, что я решил сублимировать всё в коде и написал первую версию wiki-системы для lisper.ru: http://lisper.ru/wiki/. Возможности, конечно, весьма тривиальны, но работать уже можно. В качестве языка разметки используется систаксис dokuwiki, для которого реализована довольно полная поддержка (не всё ещё, но, в основном, работает правильно).

Разработка данной версии заняла примерно 6 часов (с 10 вечера до 4 утра).

вторник, 11 августа 2009 г.

Перевод PCL на lisper.ru

В предыдущем сообщении я написал о желании публиковать перевод Practical Common Lisp на lisper.ru. Двое суток тяжелого труда :) и вот, уже можно смотреть: http://lisper.ru/pcl/. Там ещё много работы по доводке, но все принципиальные проблемы решены.

Попутно начал писать фрэйморк для парсинга всяких лёгких wiki-разметок: https://github.com/archimag/wiki-parser/tree (код ещё сырой).

суббота, 8 августа 2009 г.

Common Lisp vs PHP

Есть у меня мысль реализовать возможность просмотра перевода "Practical Common Lisp" на lisper.ru. Для этого нужен парсер dokuwiki-разметки. Сам я в парсерах, мягко говоря, не силён. Но больше его никто писать не хочет :( Этот парсер дал бы другие возможности, в том числе, возможность экспорта PCL в разные форматы и мог бы послужить основой для разработки вики для lisper.ru.

Ну вот, поскольку больше некому, то решил попробовать сделать сам. Полез смотреть как это делали другие. Взял оригинальный код парсера dokuwiki (PHP) и код cl-markdown (Common Lisp), по сути, очень близкие проекты. Код cl-markdown смотрится, в общем, нормально, но это если не сравнивать с кодом парсера dokuwiki: разница в простоте и ясности кода просто поражает (100 баллов в пользу PHP, при том, что я никогда не писал на PHP, и вообще никогда с ним дела не имел).

Мораль: да, Common Lisp велик и могуч, а PHP ничтожен, но это в теории, а на практике же бывает совсем иная картина. В общем, очередной раз убедился, что все рассказы об удивительных свойствах lisp, равно как и ФП должны потверждаться на практике. Пока же, PHP уходит в отрыв...

Символьные вычисления

Если вы интересовались языком Lisp, то наверняка читали, что он был придуман для символьной обработки данных. Я такое тоже читал, но долго не мог понять, что же всё таки скрывается за этим загадочным термином "символьные вычисления". Однако, там же, где упоминается данная область применений Lisp, обычно есть подсказка в виде какой-либо версии вычислителя s-выражений (калькулятора), плюс, вы могли слышать (знать/пользоваться) о замечательной системе символьных алгебраических вычислений Maxima, которую часто приводят в качестве примера системы, реализованной на Common Lisp. Всё это невольно подталкивает к мысли, что "символьные вычисления" имеют какое-то отношение к алгебре, s-выражениям (что за зверь?) или "типо того", этим возможно занимаются какие-то безумные учёные из NASA, но это, совершенно точно, не имеет никакого отношения к базам данных, веб-страницам, и прочим вещам, за которые вам на самом деле платят деньги. Ну, не знаю как вы, а я думал так.

Однако, не смотря на то, что я так думал, я всё равно занимался изучением Common Lisp, ибо он обладает целым набором замечательных свойств (перечислять не буду). И вот, по мере изучения Common Lisp я вдруг стал понимать, что "символьные вычисления" это совершенно нечто иное и лучше бы мне не показывали этот самый вычислитель s-выражений и не рассказывали про Maxima: может тогда бы я осознал силу и пользу "символьных вычислений" гораздо раньше.

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

В Common Lisp символы - это фундаментальный тип данных, для которого существую мощный API, являющийся результатом 50-ти лет развития. Т.е. я трактую обсуждаемый термин весьма прямолинейно, как операции над символами в терминах Common Lisp. И это имеет отношение и к базам данных, и к веб-страницами и вообще, к очень многому, ибо "символьные вычисления" оказываются весьма полезными при решения самых разнообразных задач. Конечно, в символах нет "джина", который выскочит и порешает все ваши проблемы, и да, их можно заменить какими-нибудь строками или "типа того", и возможно вы сейчас так и делаете (на какой-нибудь Java), или делали раньше, но это типичное "велосипедостротильство", со всем его недостатками.

Вряд ли у вас прибавилось понимания "символьных вычислений" :), для этого нужно много практических примеров и я собираюсь написать статью об использовании "символьных вычислений" при разработке web-приложений и выложить её на lisper.ru. Правда, вряд ли это будет раньше, чем через пару месяцев.

P.S. Широко распространено заблуждение, что самой-самой главной фичей Common Lisp являются макросы. Это не так. Макросы это замечательно, но это не "самая главная фича" (такой просто нет). Но, между прочим, макросы возможны в том числе потому, что CL поддерживает символьные вычисления.

четверг, 6 августа 2009 г.

cl-routes на github

Продолжаю перевод проектов с Google Code SVN на github. Очередная жертва - cl-routes, теперь исходный код живёт здесь

пятница, 31 июля 2009 г.

lisper.ru

Ресур http://lisp.catap.ru теперь доступен на http://lisper.ru. И все запросы на lisp.catap.ru перенаправляются туда. Пока ресурс функционирует в режиме альфа-версии, но уже можно пользоваться (тестировать и сообщать об проблемах).

Напоминаю, кто пропустил, что написано на CL.

пятница, 17 июля 2009 г.

Первый запуск wishfeeds.com

Запустили самую первую, самую что ни есть "альфу" нового сервиса http://wishfeeds.com.

В интеренет сейчас достаточно много "планет" (например - Planet Lisp): подборок блогов на заданную тему. Обычно подобные сервисы организуют на софте от http://www.planetplanet.org/. Т.е. для организации подобного сервиса нужно быть админом и иметь сервер в интеренет. Цель сервиса http://wishfeeds.com в том, что бы дать пользвоателям возможность самостоятельно и без каких-либо усилий организовывать свои собственные планеты. Идея в том, что бы дать возможность "экспертам", знающим много интересных блогов (вообще говоря, не только блогов), собирать из них интересные тематические планеты, которые станут доступны для всех. Например, http://wishfeeds.com/archimag/russian-lisp-planet/ - та же самая Russian Lisp Planet, за тем исключением, что для её создания мне потребовалось всего несколько минут и я сделал её полностью через предоставленный web-интерфейс.

Сервис находится в самой ранней стадии развития, но нам хотелось бы получить некоторое колличество пользователей, для получения какой-либо обратной связи. Однако, поскольку сервис является довольно ресурсоемким, то пока мы не может открыть свободную регистрацию. Поэтому, желающим зарегестрироваться просьба писать мне (можно в комментарии), либо я создал специальную группу http://groups.google.ru/group/wishfeeds-user/topics, где можно обсуждать работу сервиса и попросить об регистрации.

P.S. Тэг lisp потому, что написано на lisp :)