четверг, 27 ноября 2008 г.

cl-libxml2-0.2.1

Выпустил небольшой релиз cl-libxml2-0.2.1, сделал страницу с примерами кода и все таки, наконец, завел отдельную страницу на cliki: http://www.cliki.net/cl-libxml2.

пятница, 21 ноября 2008 г.

cl-libxml2-0.2

Сегодня выпустил cl-libxml2 версии 0.2

Основное внимание при разработке этого релиза было уделено возможностям расширения, это:
  • Custom URL resolve

  • XPath extension functions

  • XSLT extension elements

Custom URL resolve

Позволяет подсовывать парсеру, когда он желает загрузить какие-то внешние данные, свои данные. Это может потребоваться для реализации какого-то нестандартного протокола, либо, например, для поддержки авторизации, ну да много для чего. Выглядит это примерно так:
CL-USER> (in-package #:libxml2.tree)
#<PACKAGE "LIBXML2.TREE">
TREE> (defun my-resolve-1 (url id ctxt)
(declare (ignore url id))
(resolve-string "<node />" ctxt))

MY-RESOLVE-1
TREE> (defun my-resolve-2 (url id ctxt)
(declare (ignore id))
(if (eql (puri:uri-scheme url) :my)
(resolve-string (format nil "<node>~A</node>" url) ctxt)))

MY-RESOLVE-2
TREE> (with-custom-resolvers (#'my-resolve-2 #'my-resolve-1)
(with-parse-document (doc "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">
<xi:include href=\"my:doc\" />
<xi:include href=\"my2:doc\" />
</root>")
(process-xinclude doc)
(serialize doc :to-string)))

"<?xml version=\"1.0\" encoding=\"utf-8\"?>
<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">
<node>my://doc</node>
<node/>
</root>
"

XPath extension functions

XPath мощный инструмент, но часто встроенной функциональности не хватает. В таком случае можно добавить пару своих функций:

TREE> (in-package #:libxml2.xpath)
#<PACKAGE "LIBXML2.XPATH">
XPATH> (define-xpath-function hello-world ()
"Hello world!")

HELLO-WORLD
XPATH> (define-xpath-function echo (msg)
msg)

ECHO
XPATH> (define-xpath-function join (delimiter &rest strs)
(iter (for str in strs)
(reducing str
by (lambda (s x) (concatenate 'string s delimiter x)))))

JOIN
XPATH> (with-xpath-functions ((hello-world "hello-world")
(echo "echo")
(join "join"))
(with-parse-document (doc "<root />")
(find-string doc "join('//', hello-world(), '---', echo('Buy!'))")))

"Hello world!//---//Buy!"

XSLT extension elements

Я очень люблю писать на XSLT, совершенно удивительный инструмент, однако и здесь набор имеющихся средств далеко не полон. Не беда, дописать необходимые элементы можно и самому. Например, встроенную функцию copy-of можно реализовать так:

XPATH> (in-package #:libxml2.xslt)
#<PACKAGE "LIBXML2.XSLT">
XSLT> (define-xslt-element copy-of (self input output)
(iter (for node in-xpath-result (attribute-value self "select") on input)
(append-child output (copy node))))

COPY-OF
XSLT> (with-xslt-elements ((copy-of "copy-of" "www.sample.org"))
(with-stylesheet (style "<?xml version=\"1.0\"?>
<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:my=\"www.sample.org\" extension-element-prefixes=\"my\" version=\"1.0\">
<xsl:template match=\"/root\">
<result><my:copy-of select=\"node()[@attr]\" /></result>
</xsl:template>
</xsl:stylesheet>")
(with-parse-document (doc "<root><a attr=\"1\"/><b /><c attr=\"2\"/><d /></root>")
(with-transfom-result (res (style doc))
(serialize res :to-string)))))

"<?xml version=\"1.0\" encoding=\"utf-8\"?>
<result>
<a attr="1"/>
<c attr="2"/>
</result>

Резюме

Кстати, "custom URL resolving" и "XPath extension functions" работают и внутри XSLT.
Когда-то меня впечатлили соответствующие возможности библиотеки lxml: оказалось что реализовать сопоставимый функционал не так уж и сложно :-)

среда, 12 ноября 2008 г.

Управление ресурсами в Common Lisp

Вот за что люблю C++, так это за деструкторы (ну, конечно, не только за это). Последовательное следование принципу RAII привело к тому, что в течении последних нескольких лет активного использования C++ я не имел ни одной проблемы, связанной с утечкой ресурсов, даже когда писал с использование ObjectARX (Autocad), а это жуткий монстр, настоящее минное поле: шаг в сторону (ну в документации то про это ни слова) и все - лезут проблемы, но в каком-нибудь совершенно другом месте... Поэтому все байки о том, что при программировании на C++ есть какие-то большие проблемы с управлением ресурсами никогда всерьез не принимал: так было когда-то давно (ну когда компьютеры были большими, а программы маленькими), сейчас же достаточно просто последовательно использовать современным концепциям (RAII, интеллектуальные указатели, безопасная обработка исключений) и все эти рассказы о трехсуточных отладках можно смело отнести к области программистского фольклора былых дней.

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

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

"Lisp way" для решения этих проблем заключается в использовании макросов with-... Часто такой способ и удобен и эффективен. Но не всегда. Возьмем такой надуманный (и довольно глупый) пример:
(defun myprint (text stream)
(write text :stream stream))
Что будет, если в качестве stream будет передан nil? Ну, текст будет выведен в *standard-output*. А если необходимо другое? Скажем, как нибудь так:
(defun myprint (text stream)
(if stream
(write text :stream stream)
(with-open-file (out (print (sb-posix:mktemp "/tmp/XXXXXX")) :direction :output)
(write text :stream out))))
Т.е. мы получили дублирование кода. Благо это пока только одна строка (write text :stream stream). А если там не одна строка? А много-много строк? Ну хорошо, можно сделать так:
(defun myprint (text stream)
(flet ((myprint-impl (text stream)
(write text :stream stream)))
(if stream
(myprint-impl text stream)
(with-open-file (out (sb-posix:mktemp "/tmp/XXXXXX") :direction :output)
(myprint-impl text out)))))
Вроде всё хорошо? Ну а если в функцию передается несколько потоков и каждый из них может иметь значение nil и необходимо для каждого такого параметра создавать поток самостоятельно. Вот тут начинаются проблемы, ибо with-open-file нам больше не поможет (разве что только через совершенно жуткий код, пример которого я даже приводить боюсь). Нет, ну по настоящему мужественные люди возьмут в руки unwind-protect, что фактически эквивалентно переходу на ручное управление ресурсами, и без особых проблем напишут необходимый код. Только я так делать боюсь. Ибо это требует аккуратного кодирования, а славное прошлое C++-программиста научило меня избегать подобных вещей.

Подобной ситуации с потоками у меня пока ещё не было (да и вряд ли будет, пример то надуман), но схожие ситуации при работе, например, с cffi встречают регулярно, и выглядит это примерно так:
(defun myfun (str1 str2 str3 str4)
(cffi:with-foreign-strings ((%str1 str1) (%str2 str2) (%str3 str3) (%str4 str4))
(c-фунция %str1 %str2 %str3 %str4)))
Всё отлично, вот только с-функция может принимать, как это часто бывает, в том числе NULL-ы, а значит в myfun можно передать несколько nil. Для передачи NULL в с-функцию необходимо использовать (cffi:null-pointer), но вот беда - cffi:with-foreign-strings ломается, если туда передавать nil. Приходиться брать в руки unwind-protect и заниматься ручным управлением памятью, т.е. почувствовать себя "многострадальным С-программистом", со всеми вытекающими.

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

Надо же, С-хакеры оказались в этом куда более продвинуты :-) Ситуация является неприемлемой. Эта мысль привела меня к созданию garbage-pools: реализации подобия APR Pools на Common Lisp. С помощь этого несложного инструмента предыдущий пример можно написать так:
(defun myfun (str1 str2 str3 str4)
(flet ((foreign-string (str)
(if str
(gp:cleanup-register (cffi:foreign-string-alloc str)
#'cffi:foreign-string-free)
(cffi:null-pointer))))
(gp:with-garbare-pool ()
(с-функция (foreign-string str1)
(foreign-string str2)
(foreign-string str3)
(foreign-string str4)))))
garbage-pools имеет совсем небольшой API:
  • pool - класс пула ресурсов
  • with-garbare-pool - макрос создающий и разрушающий пул. Возможно использование как именованных, так и безымянных пулов, например:
    (with-garbare-pool ()
    (cleanup-register myobj clenup-fun))
    (with-garbare-pool (mypool)
    (cleanup-register myobj clenup-fun mypool))
  • cleanup-register - регистрирует в пуле объект и "уничтожающую"-функцию
  • cleanup-pool - вызывает разрушение всех зарегистрированных объектов
  • cleanup-object - вызывает "преждевременное" разрушение зарегистрированного объекта
  • object-register - "generic"-метод для регистрации объектов в пуле без указания функции "разрушителя". Это имеет смысл, когда функцию-разрушитель можно определить по типу объекта (из коробки поддерживаются только stream и pool)
  • defcleanup - макрос, позволяющий сопоставить указанному классу функцию-разрушитель (после чего объекты данного классы можно будет регистрировать с помощью object-register). Пример:
    (defcleanup stream #'close)


Lisp Logo

Довольно случайно наткнулся на забавный Lisp Logo. Выглядит это так (на самом деле там несколько вариантов):



Такая картинка показалась мне гораздо интереснее, чем унылый



Посему, оное "страшное" предупреждение и было добавлено на главную страницу cl-libxml2 :-)

вторник, 28 октября 2008 г.

cl-libxml2-0.1

Спустя две недели после начало работы (в основном, ночной), свершилось - cl-libxml2 версии 0.1 доступна для использования, критики и прочего ... Это, конечно, только начало, но с этим уже вполне можно работать, а самое главное есть вполне вменяемая поддержка xpath и xslt.

И надо же было так совпасть, что релиз совпал с моим днем рождением :-) Типа сделал сам себе подарок :-)

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

cl-libxml2

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

В данном случае, речь идёт о недоступности полноценного решения для работы с XML. Да, cxml, вкупе с cxml-stp - отличная библиотека, но там не реализовано критически важная для меня поддержка XSLT. В принципе, близка к первому релизу Xuriella XSLT (основанная на cxml), но, как мне сообщил один из разработчиков, с производительностью у этой библиотеки пока беда... Это, вероятно, поправимо, но со временем, причем, не вполне определённым. Вообще, мне представляется довольно странным, что в открытом доступе нет обёрток для libxml2. Мне кажется, что есть некоторая нелюбовь lisp-хакеров к написанию оберток над библиотеками, написанными на других языках, возможно некоторым это кажется унизительным :-)

В общем, отчаявшись найти устраивающее меня решение для работы с xml я наконец решился начать делать самому cl-libxml2. В основу я положил CFFI, а в дизайне ориентируюсь на cxml-stp, за тем исключением (весьма существенным), что для обработки списков (элементов, атрибутов и т.д.) будут использоваться решения на базе iterate.

воскресенье, 31 августа 2008 г.

Забавно, сегодня предложили разместить в своем блоге вот это:
Клавогонки.Ру
Размещаю, хотя результат ещё слабоват :-)