Регистрация | Войти
Lisp — программируемый язык программирования
do-backup, программа создания резервных копий

С недавнего времени озаботился созданием резервных копий того, что есть на диске. Точнее, на диске сервера. Конфигурации, некоторое состояние и тому подобное. Не помню почему, но то, что было - не устроило. Ну и решил написать своё решение, но уже на лиспе. Для запуска внешних программ используется библиотека external-program. Программа do-backup обрабатывает конфигурацию вида:

((:contact

(:to "root")

(:subject "DO-BACKUP robot"))

(:action :7z

(:suffix ".7z")

(:program "7z")

(:argument "a")

(:argument :file)

(:argument :files))

(:action :slapcat

(:program "slapcat"))

(:action :pg_dumpall

(:program "su")

(:argument "-")

(:argument "postgres")

(:argument "-c")

(:argument "pg_dumpall"))

(:backup

(:action :7z)

(:file "/etc/")

(:file "/var/lib/bind/")

(:file "/var/lib/egroupware/")

(:file "/var/lib/ejabberd/")

(:file "/var/lib/ldap/")

(:dynamic-file "/var/backups/ldap-data.ldif" :action :slapcat :saveoutput t)

(:dynamic-file "/var/backups/postgresql/postgre.sql" :action :pg_dumpall :saveoutput t)

(:output-directory "/backups/configuration/" "configuration-~a.7z" 60))

(:backup

(:action :7z)

(:file "/var/lib/projects/")

(:output-directory "/backups/projects/" "projects-~a.7z" 30)))

Секция :backup описывает то, что необходимо будет резервнокопировать (в секциях :file и :dynamic-file), а :action - какие дейтвия необходимо выполнять. В целом, как мне кажется, по конфигурации понятно что должно происходить.

Далее, необходимо запускать задания по расписанию. В целом, мог бы подойти и обычный cron, но в данном случае для обобщения решений на лиспе был использован clon как аналог cron'а. Ну и, конечно же, необходимо демонизировать. Это достигается путём использования пакета sbcl-daemon, являющегося производным от restas-daemon.lisp. Важное отличие - это неиспользование (sb-posix:umask 0), поскольку это порождает файлы с записью для всего мира, что несколько небезопасно по умолчанию. Ну и ещё динамический запуск процедур в зависимости от конфигурации. Конфигурация демона do-backup производится в /etc/do-backup.daemon, где и задаётся периодичность запуска.

В заключении - исходный код проекта. Собранные пакеты, готовые для установки - в lisp-репизотрии для Ubuntu и Debian.

*notitle*
Привет!
В общем вот какое дело:
emacs + slime + clisp + windows
пишет каракули при выводе кириллицы
я это лечу,
(set-process-coding-system (get-process "inferior-lisp") 'cp1251 'cp1251)
ну или хоткеем (просто первый вариант длиннее, ну вы понимаете)

А как это куда-нибудь вписать чтобы не приходилось этого делать вообще.
Потому, что если я явно указываю все параметры кодировок в .emacs, то он перестает автоматически распознавать и потом каракули из буфера обмена лезут и вообще.
Исходный код cliki
Из любопытства посмотрел исходный код cliki - думаю, что его стоит засекретить, что бы никто его не увидел, не ужаснулся и не объявил, что CL полное г..., особенно для разработки web-приложений.
Лисп, свои типы данных и операции над ними.
В воскресенье объехали с женой вокруг Брно (65 км), размяли ноги. Увидел на lisper.ru этот тред, решил размять и мозг.

Итак, нужно иметь свой тип для представления денег. Деньги представляются числом с фиксированным количеством разрядов после запятой. Например, 2 для копеек. Числа с плавающей точкой использовать нельзя, т.к. значения во время вычислений определённо уедут из-за округлений, плюс нельзя контроллировать кол-во разрядов после запятой.

Пусть наш тип данным будет cons'ом, в голове которого будет само значение, а в хвосте - экспонента с десятичным основанием. Голова - целочисленная, масштабированная на экспоненту. Например, 2.43 во внутреннем представлении будет (243 . 2) .


(deftype money (&optional exp)
  `(and (satisfies consp) (satisfies is-money)))

Чтобы объект был похож на деньги, он должен быть cons'ом и удовлетворять is-money:

(defun is-money (x)
  (and (integerp (car x))
       (integerp (cdr x))))

Можно ещё проверок накрутить, типа, положительной экспоненты, пределов значений экспоненты и т.п., но не будем. Проверяем:

(typep '(1012 . 2) 'money) => T

Работает. Напишем хелпер, приводящий наш формат к нормальному числу:

(defun get-real-value (x)
  (cl:/ (car x) (expt 10 (cdr x))))

и хелпер, делающий деньги:

(defun make-money (val exp)
  `(,(round (cl:* val (expt 10 exp))) . ,exp))


Проверяем:

(make-money 10.12 2) => (1012 . 2)


Работает, но '(1012 . 2) в коде программы выглядит не очень презентабельно. К счастью, у нас есть ридтейбл, модификацией которого можно добиться, чтобы лисп, например, читал конструкцию вида $10.12'2 и создавал нужный конс (1012 . 2).

(set-macro-character #\$
 (lambda (s disp)
   (declare (ignore disp))
   (let ((*readtable* (copy-readtable)))
     (set-macro-character #\'
              (lambda (foo bar)
                (declare (ignore bar))
                (read foo)))
     `(make-money ,(read s) ,(read s)))))

Здесь объявляется, что символ $ имеет специальное значение для лиспового парсера, а при попадании на него нужно вызвать означенную лямбду. В лямбде также переопределяется смысл апострофа, т.к. он присутствует в формате наших денег для отделения экспоненты от тела. Разумеется, внутри области видимости новой *readtable* квотирование через апостроф больше работать не будет, но нам и не надо.

(equal $10.12'2 '(1012 . 2)) => T

(typep $10.12'2 'money) => T

Всё ещё работает.

Разберёмся с какой-нибудь зловредной математической операцией, меняющей кол-во знаков после запятой. Пусть это будет умножение.

Т.к. функция умножения уже присутствует в системе, то её нужно затенить, дабы компилятор не ругался: (shadow :*). Функция получает список аргументов, над которым нужно произвести действие. В первом приближении я написал код вида (apply #'cl:* (mapcar деньги-в-число ...)) с последующим вызовом make-money с полученным числом и максимальной найденной экспонентой, но так делать неправильно, т.к. во время умножения будет копиться ошибка, перетекающая в старшие разряды и потенциально могущая попасть в значимый диапазон. Правильнее будет на каждой итерации приводить результат к деньгам, для чего воспользуемся техникой reduce:

(defun * (&rest args)
  (reduce
   (lambda (&rest args)
     (when args
       (apply #'make-money
          (case (length args)
        (1
         (destructuring-bind (v1 e1)
             (normalize-value (car args))
           (list v1 e1)))
        (2
         (destructuring-bind ((v1 . e1) (v2 . e2))
             (list (normalize-value (car args))
               (normalize-value (cadr args)))
           (list (cl:* v1 v2) (max e1 e2))))))))
   args))

где normalize-value:

(defun normalize-value (x)
  (if (typep x 'money)
      (cons (get-real-value x) (cdr x))
      (cons x 0)))

Проверяем:

(* $1.23'2 $2.01'2 2.0 1/2) =>  (247 . 2)

Опять работает.

Оставшиемя основные арифметические операции определяются аналогично (через macrolet). Внутреннее представление денег можно, по вкусу, изменить на структуру.
Разминка мозгов
А какие бы вы предложили красивые варианты сгенерировать рандомное поле для судоку?

Мне, как инженеру неголубых кровей, помимо тупого брутфорса идей никаких не приходит =)

(defun gen-field ()
  (let ((field (make-array '(9 9) :initial-element 0)))
    (macrolet ((cell (r c) `(aref field ,r ,c)))
      (labels ((is-valid (r c)
                 (flet ((find-in-rect (y h x w)
                          (iter (with value = (cell r c))
                                (for i from y below (+ y h))
                                (iter (for j from x below (+ x w))
                                      (for v = (cell i j))
                                      (when (and (or (/= i r) (/= j c)) (/= v 0) (= v value))
                                        (return-from find-in-rect t))))
                          nil))
                   (not (or (find-in-rect r 1 0 9)
                            (find-in-rect 0 9 c 1)
                            (find-in-rect (- r (mod r 3)) 3 (- c (mod c 3)) 3)))))
               (gen-shuffled-vec ()
                 (let ((vec (make-array 9 :initial-contents (iter (for i from 1 to 9) (collect i)))))
                   (iter (for idx index-of-vector vec)
                         (rotatef (elt vec idx) (elt vec (random (length vec)))))
                   vec))
               (fill-field (r c)
                 (if (> r 8)
                     t
                     (iter (for value in-vector (gen-shuffled-vec))
                           (setf (cell r c) value)
                           (when (and (is-valid r c)
                                      (fill-field (+ r (truncate (/ (1+ c) 9))) (mod (1+ c) 9) ))
                             (leave t))
                           (finally (setf (cell r c) 0)
                                    (return nil))))))
        (fill-field 0 0))
      field)))



Типа:


SUDOKU> (gen-field)
#2A((7 3 4 9 8 1 6 2 5)
    (5 1 9 4 2 6 8 7 3)
    (2 8 6 5 7 3 4 1 9)
    (1 7 2 6 5 4 3 9 8)
    (6 5 8 2 3 9 1 4 7)
    (9 4 3 8 1 7 2 5 6)
    (8 6 5 7 4 2 9 3 1)
    (4 9 1 3 6 5 7 8 2)
    (3 2 7 1 9 8 5 6 4))

Статья про Clojure & Hadoop
Наконец-то закончил давно начатую и откладываемую статью про использование Clojure для программирования Hadoop.  В статье описывается пакет clojure-hadoop, который позволяет сильно упростить программирование заданий для Hadoop.

P.S. English version will follow after short time, that I need to make translation ;-)
*notitle*
ASDF2 на порядок приятнее в использовании, чем предыдущая версия, по моим ощущениям. Особенно в области поиска систем. По крайней мере, на винде.

Например, мне, для того чтобы настроить поиск и компиляцию систем сразу на всех установленных реализациях(с asdf2, разумеется), достаточно было всего создать две переменные среды:

CL_SOURCE_REGISTRY = "(:source-registry (:tree "D:/lisplibs/") :inherit-configuration)"
и
ASDF_OUTPUT_TRANSLATIONS = "(:output-translations (T ("D:/lisplibs/compiled-files/" :implementation)) :inherit-configuration)"

и складывать сорцы всех систем в D:/lisplibs.

В D:/lisplibs/compiled-files/ asdf складывает скомпилированные fasl'ы, соответственно. (В такой форме, например: D:/lisplibs/compiled-files/sbcl-1.0.40-windows-x86/D/lisplibs/cffi/src/cffi-sbcl.fasl)

С теми извращениями, которыми я раньше с asdf занимался, это не сравнится.
Лисп в промышленной разработке
Месяц с копейками на новой работе, уже можно делать кое-какие выводы по поводу применимости Коммон Лиспа в реальном деле.

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

Какие моменты хорошо заметны:
  • Код у людей пишется очень быстро.
  • Ошибок, связанных с динамической типизаций, практически не встречается. Даже если код пишет новичёк, то 99% производимых им косяков имеют алгоритмический характер, либо неправильное использование синтаксиса (!). Декларация типов при этом не используется совсем.
  • На предупреждения компилятора (warnings) мало, кто смотрит. Если компилятор не встал колом, то код считается рабочим и спокойно коммитится в репозиторий. В итоге, warning'и растут, как на дрожжах, и в вагоне шелухи не видно критических предупреждений, которые, по-идее, могли бы быть и show stopper'ами. Автоматическая посткоммитная сборка и посылка diff'а лога автору коммита не особо помогает. Правда, у нас уровень предупреждений повышенный стоит... Но факт есть факт: на warning'и никто не смотрит.
  • Предупреждения компилятора народу бывают сложными для понимания. Т.е. спрашиваешь человека, почему он не исправит эту фигню, а он отвечает, что не понимает смысл предупреждения. Это от того, что человек с концепцией языка не знаком, а прошлый опыт с вариациями C/C++ объяснить суть ошибки не может.
  • Настоящий фортрановский программист напишет фортрановскую программу на любом языке. Железячники пишут на Лиспе, как на VHDL или C. Студенты с академическим C++/Java бэкграундом пишут всё на классах. Только лисперы пишут на Лиспе, как на Лиспе. В итоге, встречаются вырвиглазные конструкции, вытащенные явно из железячной логики, или, там, множество вложенных хэш-таблиц с мильёном объектов на каждый чих, которые разбирать даже с помощью инспектора неудобно. А ведь можно было построить список, который очень просто рекурсивно обходится и паттерн-матчится... Но всё это говорит только о том, что порог вхождения в Лисп очень низкий, и писать на нём работающий код может человек с опытом программирования на любом языке.
  • Лисповый код очень просто рефакторится. Есть у нас большой человек с огромным опытом, и он, в числе прочего, причёсывает дизайн системы. Улучшения колоссальных размеров укладываются в очень мало изменённого кода (DSL на DSL'е :).
  • Люди без лиспового опыта предпочитают IDE Лиспворкса. Религиозные фанатики пишут в Емаксе (хотя Slime Лиспворкс не в полной мере поддерживает). Возможно, именно благодаря привычно выглядящему окружению (кнопки, тулбары, гуи) и мощному отладчику, даже начинающие пишут, пусть некрасивый, неканоничный, но рабочий код.
  • Люди с богатым опытом opensource-разработки и для проприетарных продуктов пишут хороший код. Одно удовольствие такой код читать.
В целом, ощущения полной неприменимости или какой-то значительной проблемности при использовании Лиспа нет. Даже при синтезе железа (привет, thesz!). Даже в такой критичной к ошибкам области, как биржи, где каждая миллисекунда простоя стоит денег ;)
    Список рассылки про Clojure на русском
    Вчера был создан список рассылки для обсуждения вопросов о Clojure на русском языке. Кто заинтересован - присоединяйтесь!
    Обновление REPO 20100720
    Очередное пополение в lisp-репизотрии для Ubuntu и Debian.
    • sbcl-daemon - инфраструктура для запуска лисп-приложений под sbcl в качестве демона (начальный коммит, производный от restas-daemon.lisp);
    • trivial-timers - библиотечка таймеров, используемая в clon;
    • clon - некий аналог cron'а.
    Weblocks perfomance
    Вкрадце идея такая: если weblocks-приложение вдруг залагало, глянте M-x slime-profile-package на weblocks. Буде заметите там беду такого плана:

      seconds  |     consed    |  calls |  sec/call  |  name  
    ------------------------------------------------------------
        12.956 | 1,117,650,656 |  1,059 |   0.012234 | WEBLOCKS:ASDF-SYSTEM-DIRECTORY
         0.270 |    14,001,776 |  1,059 |   0.000255 | WEBLOCKS:MAKE-LOCAL-DEPENDENCY
         0.249 |     3,396,960 |  1,059 |   0.000236 | WEBLOCKS:COMPUTE-WEBAPP-PUBLIC-FILES-PATH
         0.045 |       821,952 |    187 |   0.000241 | WEBLOCKS:RENDER-LINK
         0.042 |     1,108,208 |  1,059 |   0.000040 | WEBLOCKS:COMPUTE-PUBLIC-FILES-PATH
         0.041 |       729,952 |    115 |   0.000361 | WEBLOCKS:DOM-CLASSES
         0.036 |     2,383,744 |    115 |   0.000315 | WEBLOCKS:RENDER-WIDGET-BODY
    ...


    -- это лечится явным заданием :public-files-path в defwebapp.

    Tnx to http://www.mail-archive.com/weblocks@googlegroups.com/msg02366.html, я сегодня нарвался.
    layman и archimag-lisp-overlay
    Немного упростил использования моего lisp-оверлея (который форк стандартного gentoo-lisp-overlay) с помощью layman (Gentoo). Теперь, для подключения моего оверлея необходимо установить layman
    $ emerge layman
    добавить http://github.com/archimag/archimag-lisp-overlay/raw/master/layman-list.xml в "список списков оверлеев" (!), который находиться в файле /etc/layman/layman.cfg, например
    overlays  : http://www.gentoo.org/proj/en/overlays/repositories.xml
    http://github.com/archimag/archimag-lisp-overlay/raw/master/layman-list.xml
    и наконец
    layman -f -a archimag-lisp
    Всё.
    *notitle*

    Оказывается, я так до сих пор и не реализовал правильно остановку нитей. Проблемы связаны с вызовом лиспового кода сишной функцией funcall: неправильно изменяется состояние нити (нить входит в лисповый код, поэтому флаг gc_safe должен быть сброшен, но этого не происходит => сборщик мусора пропускает такую нить, и начинает сборку при работающем мутаторе кучи). Также, по-видимому, неправильно обрабатывается изменение состояния нити при раскрутке стека. Видимо, gc_safe стоит вынести из структуры нити и сделать символом; это обеспечит корректное изменение при раскрутке стека и прочих изменениях состояния нити, т.к. стек динамических привязок и так поддерживается в правильном состоянии.

    Немного попилил сокеты под виндой. Они очень странные. Так, например, нельзя просто так читать, ожидать и писать в сокеты из разных нитей - если чтение/ожидание началось раньше записи, то запись в сокет не будет произведена, пока не завершится чтение/ожидание. Поэтому, например, без особых ухищрений Slime не работает в многонитевом режиме, т.к. в нем одна нить читает из сокета, а другая - пишет в сокет. Обращаться к асинхронному вводу-выводу очень не хочется.

    Более удобный запуск внешних програм external-program-extender

    Для лиспа существует возможность наиболее кроссплатформенно запускать внешние программы - external-program. В целом с задачей своей она справляется, и даже позволяет перенаправлять вывод в поток. Но вот сразу получить вывод в виде строки или же в виде списка строк, как это делает scsh - нет этого встроенного. Собственно, с целью получить подобную функциональность и написал небольшой extender. Теперь можно использовать следующую конструкцию:

    * (run/string "ls" '("-a"))

    ".

    ..

    bin

    boot

    cdrom

    .config

    dev

    etc

    home

    initrd.img

    initrd.img.old

    .kde

    lib

    lib32

    lib64

    lost+found

    media

    mnt

    proc

    root

    sbin

    selinux

    srv

    sys

    tmp

    usr

    var

    vmlinuz

    vmlinuz.old

    "

    Или, если хочется получить результат в виде списка строк, то:

    * (run/strings "ls" '("-a"))

    ("." ".." "bin" "boot" "cdrom" ".config" "dev" "etc" "home" "initrd.img"

    "initrd.img.old" ".kde" "lib" "lib32" "lib64" "lost+found" "media" "mnt"

    "proc" "root" "sbin" "selinux" "srv" "sys" "tmp" "usr" "var" "vmlinuz"

    "vmlinuz.old" "")

    Данную библиотеку можно собрать из меркуриаловского хранилища исходных текстов, или же, при использовании Ubuntu или Debian из lisp-репизотрия для Ubuntu и Debian.

    Лисп семинары в Северной Америке
    2010 семинар Scheme и функционального программирования состоится в Монреале 21-22 августа. Коллеги будут вести презентацию доклада о JazzScheme. Планирую присутствовать, и выставить впечатления здесь.

    2010 международная Лисп конференция будет в Рино, США 19-21 октября. В этом году не собираюсь.
    RESTAS: упрощение отладки удалённых серверов
    Я писал ранее, что в репозиторий Hunchentoot был принят мой патч, который должен был упростить отладку удалённых серверов. Однако, в последующем он был отменён с сообщением: "Fix breakage of LW version". LispWorks у меня нет, в чём проблема там я не знаю, но Edi мог хотя бы сообщить об этом в рассылку, а то ведь этого можно было и не заметить. Ну да ладно, немного поразмыслив решил просто перенести данный функционал в код RESTAS. Теперь отладочные режимы RESTAS и Hunchentoot независимы друг от друга. Теперь в RESTAS:
    • Постоянно поддерживается список отлаживаемых в данный момент потоков
    • Добавлен параметр *max-debugging-threads*: максимально возможное количество одновременно отлаживаемых потоков, значение по умолчанию - 5.
    • Функция debug-mode-on - активизирует отладочный режим.
    • Функция debug-mode-off - отменяет отладочный режим, имеет необязательный параметр kill-debugging-threads (по-умолчанию T) , который определяет надо ли уничтожать отлаживаемые в данный момент потоки.
    • В случая наличия в системе swank-сервера, в переменную swank::*connection-closed-hook* добавляется вызов debug-mode-off , который обеспечивает отмену отладочного режима и уничтожение отлаживаемых потоков после разрыва соединения.
    • Описанная схема применяется только к потокам, которые созданы Hunchentoot для обработки запросов.

    Презентация про Clojure с MarginCon 2010
    В конце июня я делал доклад про Clojure на конференции MarginCon (через Skype), а сегодня я выложил презентацию синхронизированную со звуком. Огромное спасибо Юре Юревичу за предоставленную запись и за возможность выступления на этой конференции...
    P.S. звук немного гуляет, в нескольких местах опущено по несколько секунд выступления, но в целом не должно быть проблемы с остальным текстом
    Новости Redis и cl-redis
    Наконец, дошли руки обновить наш клиент для Redis и доработать его для поддержки новых режимов, которые добавились в последних версиях БД. Заодно и проверить утверждения из статьи о том, что клиент тривиально расширяемый и легко адаптируется к изменениям.

    На самом деле, пришлось немного повозиться. Но не потому, что утверждения ошибочны, а из-за стандартной проблемы подавляющего большинства софтверных проектов — неадекватной документации. Впрочем, в случае с Redis'ом все не однозначно. С одной стороны, в общем, документация хорошая и правильная: во-первых, не перегруженная, во-вторых, описан протокол взаимодействия, и для каждой команды более-менее указано, как она использует протокол (а еще и другие полезные вещи: иногда примеры использования, а также, что мне понравилось, временные характеристики). Но, в том то и дело, что "более-менее" указано, и некоторые важные моменты упущены, так что в этот раз пришлось лезть в код родного клиента redis-cli и даже прослушивать его взаимодействие с сервером tcpdump'ом (жалко, что я не додумался с этого начать :)

    Короче говоря, у Redis появилась поддержка хеш-таблиц, но обнаружился баг у всех команд, которые передают ключи (в терминах Redis — "поля") в таблице (самый простой пример: HGET table field). Почему этого не заметили разработчики? Видимо, потому, что родной клиент использует для общения с сервером не описанный протокол с разными вариантами передачи команд: inline, bulk и т.п.,— а простой унифицированный способ в форме multi-bulk, т.е. вместо строки "HGET table field", которую посылал наш клиент, вот что:
    "*3
    $4
    HGET
    $5
    table
    $5
    field"

    Соответственно, наверно, и не заметили особенность при обработке полей в случае передачи команды в inline-форме.

    Справедливости ради, нужно сказать, что если порыться в документации, то можно найти замечание, что "A good client library may implement unknown commands using this command format in order to support new commands out of the box without modifications." (т.е. самый продуктивный путь был — снова перечитать спецификацию протокола :) Теперь вот, задумался, может стоит перевести все команды на эту multi-bulk форму. И API упростится, будет не:
    (def-cmd HGET (key field)
    "Retrieve the value of the specified hash FIELD."
    :generic :bulk)
    а
    (def-cmd HGET (key field) :bulk
    "Retrieve the value of the specified hash FIELD.")
    С другой стороны, немного уменьшиться производительность.

    Вообще, Redis все в большем количестве мест использует multi-bulk форму (поскольку она наиболее общая). Поддержка PubSub сделана также на ней, хотя и с небольшим отклонением.

    PubSub — это сейчас такой горячий пирожок, который все хотят съесть. Как это реализовано здесь? Есть команды SUBSCRIBE и PSUBSCRIBE, позволяющие подписаться на каналы соответственно по имени и по шаблону (типа "news.*"). И есть PUBLISH, которая посылает сообщения. Сообщения доставляются условно мгновенно и приходят в рамках тех активных соединений, в которых произведена подписка. Таким образом, типичный подход к применению этого, насколько я понимаю — это когда у нас есть отдельная нить-слушатель, которая обрабатывает приходящие сообщения и иницирует какую-то реакцию в программе. Где-то так:
    (with-connection ()
    (red-subscribe "chan")
    (loop
    (let ((msg (expect :multi)))
    (bt:make-thread (lambda ()
    (process (elt msg 2))))
    ;; или же, например
    (bt:with-lock-held (*lock*)
    (push (elt msg 2) *internal-queue*)))))
    Сообщения имеют вид '("message" <канал> <сообщение>) для простой подписки и '(<шаблон> <канал> <сообщение>) для PSUBSCRIBE. Вот, собственно, и всё.

    Как видно из примера, получение сообщений из канала блокирующее и выполняется просто за счет вызова функции expect, которая во всех обычных командах присутствует в паре tell-expect. Так что, возвращаясь к моим утверждениям про легкую расширяемость и адаптацию — они подтвердились (и один из примеров — вот он).

    Redis действительно быстро развивается и много чего меняется, добавляются радикально новые варианты использования: в данном случае транзакции и PubSub. Но для поддержки всего этого в клиенте хватает точечных изменений на уровне реализации протокола, никакого рефакторинга. В этот раз нужно было добавить еще несколько вариантов ожидания ответа: :queued (для транзакций), при котором считывается сразу несколько разнородных ответов подряд; :float; а также :pubsub,— поменять несколько определений команд, потому что поменялась сама их спецификация. Ну и добавилась обработка особого случая транзакций, когда любая команда возвращает "QUEUED" вместо своего стандартного ответа.

    PS. Да, и еще про транзакции: теперь, как видите, Redis и их поддерживает. Что меня заинтересовало — это обсуждение гарантий целостности (на этой же странице внизу), которые, на первый взгляд, недостаточны: нет условия успешного завершения всех команд в рамках транзакции, чтобы транзакция была признана успешной. Т.е. ROLLBACK не предусмотрен. Но вот, что пишет на этот счет Сальваторе Санфилиппо:
    I think you are missing the point about MULTI/EXEC, that is, a Redis command only fails on syntax error or type mismatch. That is, in code without errors the transaction will either be executed as whole or nothing. Still it is true that if there are programming errors like using a list command against a set, or a syntax error, only the sane commands will be performed and the others instead will fail, but it's very hard for this to happen in production.

    Так что транзакции в Redis кислотные по-своему, и нужно хорошо уяснить для себя их семантику, прежде чем браться применять. (И опять, возвращаясь к тому, с чего я начинал: встает проблема адекватности документации. А идеальная документация — исполняемая... ;)
    Внутреннее устройство ASDF
    Это вторая статья в серии про ASDF. Первая рассказывала про нововведения в ASDF 2.

    Итак, рассказ о внутренностях ASDF начнем с того, что меня самого испугала бы задача создать с нуля подобную систему. В данном случае в идеале нужно единомоментно получить программу, обладающую одновременно такими довольно противоречивыми характеристиками:
    • хорошо покрывающую основные варианты использования (в случае ASDF — это и средство описания систем (для их последующего распространения), и менеджер сборки)
    • простую и удобную для непосвященных в детали пользователей
    • хорошо расширяемую для того, чтобы позволить развивать сопутствующую инфраструктуру (например, такие средства как ASDF-INSTALL)
    • ну и, разумеется, сразу корректно работающую
    ASDF была впервые написана Деном Барлоу в 2001 году. Как я понимаю, подспорьем при ее создании был фундаментальный труд Кента Питмана, обобщающий опыт в этой сфере, "Описание больших систем" (1984 года), а также опыт эксплуатации ее предшественника MK:DEFSYSTEM. Т.е., по сути, ASDF была "второй системой", но в данном случае, к счастью, удалось избежать реализации соответствующего синдрома.

    Что же представляет из себя эта сама по себе довольно большая система изнутри? Хребтом ASDF является иерархия классов componentmodulesystem, которые содержат информацию об именах, местоположении и зависимостях систем и их компонент, метаинформацию, а также служебную информацию самой ASDF.

    Кроме того описаны классы операций, которые могут выполняться над системами, как то: compile-op, load-op, test-op и т.д. Почему операции являются объектами, а не просто ключами, что напрашивается на первый взгляд? Во-первых, это позволяет наследовать от них и при этом точечно менять поведение связанных обобщенных функций. Но даже более важно то, что объект операции имеет определенные свойства: операцию-родитель, является ли операция форсированной (хотя это свойство на данный момент не работает корректно), таблицы отработанных и еще не отработанных узлов и т.д. Это имеет и свой недостаток, поскольку кажется несколько избыточным для обычного пользователя. В версии ASDF 2 для его устранения введены функции-обертки load-system, compile-system и test-system.

    На уровне пользовательского API на основе всех этих классов функционирует макро defsystem, а также обобщенная функция operate.

    Defsystem — это хороший инструмент описания систем, знакомый и понятный, я думаю, каждому. Он ведет свою историю еще от Lisp Machine Lisp DEFSYSTEM, хотя с тех пор и существенно эволюционировал в сторону упрощения интерфейса.

    Параметры defsystem-формы не передаются, как можно было бы предположить, напрямую в (make-instance 'system ...), а сперва обрабатываются функцией parse-component-form. При этом часть параметров передается как есть, а часть транслируется или используется в качестве мета-параметров. Остановлюсь на двух из них:
    • defsystem можно применять не только для стандартных систем, но и их потомков за счет параметра :class
    • параметр :depends-on:weakly-depends-on1), на самом деле, не присутствует в качестве слота в классе component. Его содержимое транслируется в содержимое слота in-order-to, которое описывает зависимости более гранулярно отдельно для каждой операции. Кстати, этот слот можно задать напрямую в defsystem-описании, чем иногда пользуются при необходимости указания нестандартных сценариев поведения. Впрочем, среди установленных у меня порядка 70 библиотек я нашел только несколько примеров такого использования, самый интересный из которых — в описании системы weblocks-prevalance (в остальных случаях это применяется для указания зависимостей систем-тестовых наборов). В данном случае устанавливается зависимость от дополнительно определенной операции prepare-prevalance-op:
    (defsystem weblocks-prevalence
    :name "weblocks-prevalence"
    ;; кстати, нет смысла задавать параметр `name' для системы,
    ;; поскольку имя берется из символа, передаваемого в defsystem
    :description "A weblocks backend for cl-prevalence."
    :depends-on (:metatilities :cl-ppcre :cl-prevalence :bordeaux-threads)
    :components ((:file "prevalence"))
    :in-order-to ((compile-op (prepare-prevalence-op :weblocks-prevalence))
    (load-op (prepare-prevalence-op :weblocks-prevalence))))
    А сама операция prepare-prevalence-op характеризуется всего одним дополнительным методом, отвечающим за подгрузку дополнительной системы, находящейся по внутреннему пути, не известному в *central-registry*:

    (defmethod perform ((op prepare-prevalence-op)
    (c (eql (find-system :weblocks-prevalence))))
    (unless (find-package :weblocks-memory)
    ;; load weblocks if necessary
    (unless (find-package :weblocks)
    (asdf:oos 'asdf:load-op :weblocks))
    ;; load weblocks-memory.asd
    (load (merge-pathnames
    (make-pathname :directory '(:relative "src" "store" "memory")
    :name "weblocks-memory" :type "asd")
    (funcall (symbol-function
    (find-symbol (symbol-name '#:asdf-system-directory)
    (find-package :weblocks)))
    :weblocks)))
    ;; load weblocks-memory
    (asdf:oos 'asdf:load-op :weblocks-memory)))
    Впрочем, это можно было бы сделать и иначе :)

    Проблемой использования defsystem, которой я коснусь в следующей статье на тему шаблонов применения ASDF, является то, что есть соблазн отступить о чисто декларативного описания системы и добавить в него некоторые исполняемые элементы, например, чтение и подстановку версии системы из отдельного файла. Как по мне, было бы разумно обрабатывать эту форму в рамках (let ((*read-evel* nil) ...), чтобы исключить такие варианты. Причина тут в том, что ASDF-описание может обрабатыватся более, чем 1 раз при поиске систем и разрешении зависимостей, и работа с ним в таком случае выполняется в режиме просто чтения. Возможно, это ограничение будет со временем установлено: во всяком случае это уже обсуждалось в рассылке.

    Теперь рассмотрим функцию operate, которая является точкой входа в область собственно ядра ASDF, которое отвечает за поиск и выполнение операций над зависимыми компонентами. Она опирается на обобщенные функции traverse, роль которой — в построении плана выполнения той или иной операции, и perform, которая собственно выполняет конечные действия, будь то компиляция, загрузка файлов и т.д, а также на функцию find-system. Кроме того, интересным дополнением (неким альтер-его) perform является explain, которая только указывает, какое действие должно быть выполнено. Хорошая иллюстрация возможностей применения explain дана в статье Питмана:
    (DOLIST (STEP (SYSDEF:GENERATE-PLAN system :UPDATE))
    (SYSDEF:EXPLAIN-ACTION system STEP)
    (UNLESS (NOT (Y-OR-N-P "OK? "))
    (SYSDEF:EXECUTE-ACTION system STEP)))
    Этот код на Lisp Machine Lisp позволяет пошагово выполнять обновление системы при условии согласия пользователя на каждом шаге.

    Операция traverse реализует алгоритм поиска и разрешения зависимостей ASDF. Сам по себе он не стоит отдельного рассмотрения, но что интересно, это то, что алгоритм может обрабатывать 3 типа зависимостей:
    • привычную простую форму (:depends-on (:cl-ppcre ...))
    • версионированную форму (:depends-on ((:version :cl-ppcre "1.2.3") ...))
    • зависимость от фичи (:depends-on ((:feature :x86) ...))
    Ко второй форме мы еще вернемся в теме о поддержке версионности. А на счет третьей, то к ней относится интересный комментарий в коде ASDF: "Congratulations, you're the first ever user of FEATURE dependencies! Please contact the asdf-devel mailing-list." :)

    Точкой входа в механизм поиска Лисп-систем в операционной системе является функция find-system, которая смоделированна на основе стандартной функции find-class. (Артефактом такого подобия является параметр errorp, необходимость в котором как здесь, так и в find-class и find-method, в которых также реализован этот подход, как по мне, по меньшей мере сомнительна). Find-system одновременно проверяет наличие системы среди уже загруженных (таблица таких систем со врменем последнего обновления находится в *defined-systems*), а также ищет на диске с помощью функции system-definition-pathname, которая в свою очередь раскручивает механиз поиска систем функциями, заданными в списке *system-definition-search-functions*. На данный момент в этом списке 2 основных функции: "классический" поиск в директориях, заданных в *central-registry*, и новый поиск в source-registry. Очень важной особенностью find-system, про которую не нужно забывать, это ее побочный эффект — загрузка ASD-файла системы в память и регистрация его в *defined-systems*.

    Наконец, стоит еще упомянуть 2 обобщенные функции output-files и input-files, которые позволяют задавать способ точного определения полных имен файлов разных типов компонент по их короткому имени в описании системы.

    В общем-то, это и всё ядро ASDF. Остальное — это ряд утилит для работы с операционной системой, среди которых несколько очень полезных функций, заслуживающих более широкой известности в Лисп-мире (например, run-shell-command, load-pathname, parse-windows-shortcut и другие), а также добавленные в ASDF 2 механизмы определения местонахождения FASL-файлов (в чем-то аналог ASDF-BINARY-LOCATIONS, для которого добавлен и compatibily mode) и работы с центральным реестром.



    Разобравшись во внутренностях ASDF я пришел к довольно неожижанному для себя выводу: в ее основе лежит хорошо продуманный объектно-ориентированный дизайн, дающий воможность для ее эффективного применения не только непосредственно, но и как основы для других инструментов. Более того, используя ее как кейс, можно даже учить людей настоящему практическому объектно-ориентированному проектированию2. В то же время, этот дизайн, конечно, не идеален.
    • Во-первых, он сложен. И это действительно оправдано причинами, описанными вначале. Но все же временами наблюдается излишняя сложность. (Впрочем, эта проблема постепенно устраняется по мере развития кода). Сложность приводит к багам, некоторые из которых существуют до сих пор, на 10-м году жизни системы.
    • Во-вторых, он расширяемый, но тоже не до конца: ядро системы создано с оглядкой на последующую расширяемость, но в поддерживающем слое об этом иногда забывали.
    • В-третьих, он плохо описан. И это, пожалуй, самая большая проблема ASDF и хороший урок для любого проектировщика: ясная и полная документация имеет важнейшее значение для удачного использования сколь угодно хорошого дизайна.

    И еще можно сказать, что ASDF намного ближе по своей философии к (качественным) продуктам "классических" системных языков, таких как С++ или Java, чем к распространенному в последнее время в Лиспе bottom-up стилю. В то же время, за счет использования полезных возможностей Лиспа: мультиметодов, функций высших порядков и т.п.,— он намного менее церемониален и многословен, так сказать, без перегрузки шаблонами проектирования.

    В следующей статье — о некоторых шаблонах использования ASDF.


    1 разница в том, что изменение "слабых" зависимостей не вызывает перекомпиляцию зависящих от них компонент
    2 ... а не такому далекому на поверку от реальности, который можно увидеть, например, в знаменитой книге Гради Буча


    И в придачу, непонятная визуализация того, как происходит поиск системы:)
    По итогам командировки...
    ... и всему увиденному заявляю, что в нашей конторе почти весь софт написан на Лиспе (Коммон, который), как то:
    • как водится, конпелятор DSL. В Лисп и VHDL. Ага, из одного исходника сразу и для железки, и для софтовой эмуляции;
    • билд-система;
    • фреймворк для эмуляции и верификации;
    • GUI;
    • CLI-утилиты;
    • железнодорожные составы библиотек на все случаи жизни;
    Не на Лиспе написаны ядра для ПЛМ, да штуки 3 башевских скрипта, в итоге всё равно вызывающих Лисп.

    Кажный божий день учусь чему-нибудь новому у более опытных коллег, ну и сам, бывает, подсказываю полезности. Интервал деятельности - от созерцания времянных диаграмм проэмулированного VHDL, через парсеры сетевых протоколов и, как водится, самый обычный системный софт до того самого конпелятора. Самое прямое участие в обсуждении архитектур не только своих продуктов, но и смежных. Ну и куда ж без линуксового ядра, ядро тоже есть... Скучать вот времени нет. Одним словом, стартап!

    Удивительно, но за такое приятное времяпровождение ещё и деньги платют!

    Надо будет как-то написать пост про наши замечательные продукты.
    Подсветка синтаксиса в nano и mc
    Порой просто не хочется запускать Emacs для того чтобы просто посмотреть исходник на LISP. И раз уж я ССЗБ то пользовался для просмотра midnight-commander или nano. Никакой подсветки LISP там и в помине нет. Ну вот однажды и надоело мне сидеть у камина с бокалом кьянти и мечтать как бы кто сделал для меня это благо. Я закатал рукава и сделал так:

    Для mc

    1. В первую очередь нудно включить встроенный в mc редактор. Он уже имеет подсветку синтаксиса, но знает лишь пару десятков команд.
    2. Заменим в файле /usr/share/mac/syntax/lisp.syntax на такое содержимое:

    wholechars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-*0123456789


    context default
        keyword whole apply yellow
        keyword whole and yellow
        keyword whole car yellow
        keyword whole caar yellow
        keyword whole cadr yellow
        keyword whole cdar yellow
        keyword whole cddr yellow
        keyword whole cdr yellow
        keyword whole close yellow
        keyword whole cond yellow
        keyword whole cons yellow
        keyword whole count yellow
        keyword whole defvar brightcyan
        keyword whole defstruct brightcyan
        keyword whole defun yellow
        keyword whole do yellow
        keyword whole eq yellow
        keyword whole eql yellow
        keyword whole equal yellow
        keyword whole eval yellow
        keyword whole format yellow
        keyword whole if yellow
        keyword whole let* yellow
        keyword whole let yellow
        keyword whole list yellow
        keyword whole load yellow
        keyword whole make-package yellow
        keyword whole mapcar yellow
        keyword whole not yellow
        keyword whole null yellow
        keyword whole numberp yellow
        keyword whole open yellow
        keyword whole or yellow
        keyword whole pprint yellow
        keyword whole prin1 yellow
        keyword whole princ yellow
        keyword whole print yellow
        keyword whole provide yellow
        keyword whole read yellow
        keyword whole require yellow
        keyword whole set yellow
        keyword whole setf yellow
        keyword whole setq yellow
        keyword whole slot-value yellow
        keyword whole sort yellow
        keyword whole stringp yellow
        keyword whole terpri yellow
        keyword whole write yellow
        keyword whole defmacro brightcyan
        keyword whole case yellow
        keyword whole progn yellow
        keyword whole when yellow 
        keyword whole loop yellow
        keyword whole unless yellow
        keyword whole with-output-to-string yellow
        keyword whole labels yellow
        keyword whole return-from yellow
        keyword whole handler-case yellow
        keyword whole defmethod yellow
        keyword whole eval-when yellow
        keyword whole declare yellow
        keyword whole with-open-file yellow
        keyword whole multiple-value-bind yellow
        keyword whole defclass brightcyan
        keyword whole with-standard-io-syntax yellow
        keyword whole getf yellow
        keyword whole mapcar yellow
        keyword whole remove-if-not yellow
        keyword whole dolist yellow
        keyword whole defparameter brightcyan
        keyword whole defgeneric yellow
        keyword whole consp yellow
        
        keyword whole lambda red
        keyword whole for red
        keyword whole nil brightred
        keyword whole t brightred
        keyword whole in-package brightred
        keyword whole defpackage brightred
        keyword whole error brightred


        keyword #' brightmagenta
        keyword ' brightmagenta
        keyword , brightmagenta


        keyword ( brightcyan
        keyword ) brightcyan


    # Keyword declarations and keywords


        keyword :\[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-*0123456789\] white
        keyword &\[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-*0123456789\] brightgreen


    # Comments


    context ; \n brown


    # Strings


    #context \* \* brightmagenta
    context " " brightgreen
    keyword \\" green
    keyword \\\\ green


    3. В итоге получим такую картинку


    В mc редактор достаточно туповат, но умеет подсвечивать парные скобки. 

    Но если вы как и я используете nano, то для вас путь особый! По умолчанию он вообще не знает о существовании Common-Lisp, по этому:

    1. Создаем в /usr/share/nano/ файл описания подсветки синтаксиса
    #touch /usr/share/nano/lisp.nanorc
    2. В него вписываем такое содержимое:

    ## Here is an example for Common-Lisp.
    ## Adapted from http://cl-cad.blogspot.com
    ## (litetabs@gmail.com)
    syntax "lisp" "\.lisp$"  "asd" "\.asd$"
    color green "\<(defun|with-open-stream|format|setf|apply|and|car|caar|cadr|cdar|cddr|cdr|close|cond|cons|count|do|eql|equal|eval|if|let*|let|list|load|make-package|mapcar|not|null|numberp|open|or|pprint|prin1|princ|print|provide|read|require|setq|slot-value|sort|stringp|terpri|write|case|progn|when|loop|unless|with-output-to-string|labels|return-from|handler-case|eval-when|declare|with-open-file|multiple-value-bind|with-standard-io-syntax|getf|remove-if-not|dolist|consp)\>"
    color red "\<(error|else|for|in-package|defpackage|defsystem|lambda)\>"
    color cyan "\<(defclass|defstruct|defvar|defconstant|defmethod|defgeneric|defparameter)\>"
    color brightred "\<(t|nil)\>"
    icolor yellow "\:\{?[0-9A-Z_!@#$*?-]+\}?"
    color brightgreen ""(\\.|[^"])*""
    icolor blue "\*\{?[0-9A-Z_!@#$*?-]+\}?"
    color cyan "(^|[[:space:]]);.*$"
    3. Для того чтобы nano узнал о новом синтаксисе, открываем файл /etc/nanorc
    #: nano /etc/nanorc
    4. В самый конец вписывает строку:

    ## Common-Lisp files
    include "/usr/share/nano/lisp.nanorc"


    Теперь можно любоваться результатом


    Плюс nano в том что без лишней крови подсвечивает и asd файлы и в нормальной навигации по файлу клавишами. Из минусов - нет подсветки парных скобок.


    Это лишь то что сразу бросалось в глаза, по этому над подсветкой синтаксиса еще стоит поработать. В настройках есть еще куча косяков, так что для отправки разработчикам они пока не годятся, но вот пользоваться можно уже сейчас. Надеюсь среди читателей найдутся те у кого завалялись пара-тройка дельных советов.
    Made with Common Lisp 2: screencast
    Я уже показывал скриншот моего нового интерфеса, но тогда не было скринкаста - обещал его чуть попозже. Вот, теперь могу кое-что показать:



    Некоторые товары отображаются не очень качество, но это проблема Chronium - с Firefox и Opera всё отображается прекрасно, но на Linux они подттормаживают (на Windows всё работает прекрасно).
    Уникальные технологии Common Lisp
    Написано для: developers.org.ua
    Время написания: октябрь 2008

    Базовые подсистемы языка

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

    Что это за технологии и какие возможности дает их использование?

    Макросистема

    • Это основная отличительная особенность Common Lisp, выделяющая его среди других языков. Ее реализация возможна благодаря использованию для записи Lisp-програм s-нотации (представления программы непосредственно в виде ее абстрактного синтаксического дерева). Позволяет программировать компилятор языка.

    • Позволяет полностью соблюдать один из основополагающих принципов хорошего стиля программирования DRY (не-повторяй-себя).

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

    Примеры применения:
    1. Определение управляющих конструкций языка, которые могут использоваться на равне со стандартными (на самом деле практически все стандартные управляющие конструкции также являются макросами. Основу языка — “аксиомы”, которые невозможно определить через другие конструкции — составляют специальные операторы). В качестве примера можно привести анафорические управляющие конструкции (см. библиотеку Anaphora), которые, используя принцип “convention over configuration”, скрывают реализацию некоторых типичных шаблонов.

      Самый простой пример — макро AIF (или IF-IT), которое тестирует первый аргумент на истинность и одновременно привязывает его значение к переменной IT, которую, соответственно, можно использовать в THEN-clause:

      (defmacro aif (var then &optional else)
      `(let ((it ,var))
      (if it ,then ,else)))

      Учитывая то, что в CL ложность представляется константой NIL, которая также соответствует пустому списку, такая конструкция, например, часто применяется в коде, где сначала какие-то данные аккумулируются в список, а потом, если список не пуст, над ними производятся какие-то действия. Другой вариант, это проверить, заданно ли какое-то значение и потом использовать его:

      (defun determine-fit-xture-type (table-str)
      "Determine a type of Fit fixture, specified with TABLE-STR"
      (handler-case
      (aif (find (string-trim *spacers* (strip-tags (get-tag "td" (get-tag "tr" table-str 0) 1)))
      *fit-xture-rules* :test #'string-equal :key #'car)
      (cdr it)
      'row-fit-xture)
      (tag-not-found () 'column-fit-xture)))

      * В этой функции проверяется, есть ли во второй ячейке первой строки HTML таблицы какие-то данные и в соответствии с этим определяется тип привязки для Fit-теста. Переменной it присвоены найденные данные.

    2. Создание DSL‘ей для любой предметной области, которые могут иметь в распоряжении все возможности компилятора Common Lisp. Ярким примером такого DSL’я может служить библиотека Parenscript, которая реализует кодогенерацию JavaScript из Common Lisp. Используя ее, можно писать макросы для Javascript!
      (js:defpsmacro set-attr (id attr val)
      `(.attr ($ (+ "#" ,id)) ,attr ,val))

      * Простейший макрос-обертка для задания аттрибутов объекта, полученного с помощью селектора jQuery

    3. В форме локальных макросов (MACROLET) для модуляризации и разделения потоков вычислений внутри сложных функций, а также для соблюдения принципа DRY при написании лишь слегка отличающегося кода в различных местах одной функции.

    4. Наконец, создание инфраструктурных систем языка. Например, с помощью макросов можно реализовать продления (библиотека CL-CONT), ленивые вычисления (библиотека SERIES) и т.д.

    5. …ну и для многих других целей.
    Больше по теме: Paul Graham, On Lisp

    Мета-объектный протокол и CLOS

    • Основа объектной системы языка. Позволяет манипулировать представлением классов.

    • Методы не принадлежат классам, а специализируются на них, что дает возможность элегантной реализации множественной диспетчиризации. Также возможна специализация не по классу, а по ключу.

    • Уникальной является технология комбинации методов, позволяющая использовать стандартные способы комбинации: перед, после, вокруг,— а также определенные пользователем.

    Примерами использования мета-объектного протокола также являются инфраструктурные системы языка, реализованные в виде библиотек:

    • object-persisance: Elephant, AllegroCache
    • работа с БД: CLSQL
    • интерфейс пользователя: Cells

    Библиотека CLSQL создана для унификации работы с различными SQL базами данных. Кстати, на ее примере можно увидеть проявление мультипарадигменности Common Lisp: у библиотеки есть как объектно-ориентированный интерфейс (ORM), реализованный на основе CLOS, так и функциональный (на основе функций и макросов чтения).

    С помощью мета-объектного протокола стандартный класс языка расширяется специальным параметром — ссылкой на таблицу БД, к которой он привязан, а описания его полей (в терминологии Lisp: слотов) — дополнительными опциональными параметрами, такими как: ограничение уникальности, ключа, функция-преобразователь при записи и извлечении значения из БД и т.д.

    Больше по теме: Gregor Kiczales et al. The Art of Metaobject Protocol

    Система обработки ошибок / сигнальный протокол

    Система обработки ошибок есть в любом современном языке, однако в CL она все еще остается в определенном смысле уникальной (разве что в C# сейчас вводится нечто подобное). Преимущество этой системы заключается опять же в ее большей абстрактности: хотя основная ее задача — обработка ошибок, точнее исключительных ситуаций,— она построена на более общей концепции передачи управления потоком выполнения программы по стеку. …Как и системы в других языках. Но в других языках есть единственный предопределенный вариант передачи управления: после возникновения исключительной ситуации стек отматывается вплоть до уровня, где находится ее обработчик (или до верхнего уровня). В CL же стек не отматывается сразу, а сперва ищется соответствующий обработчик (причем это может делаться как в динамическом, так и в лексическом окружении), а затем обработчик выполняется на том уровне, где это определенно программистом. Таким образом, исключительные ситуации не несут безусловно катастрофических последствий для текущего состояния выполнения программы, т.е. с их помощью можно реализовать различные виды нелокальной передачи управления (а это приводит к сопроцедурам и т.п.) Хорошие примеры использования сигнального протокола приведены в книге Practical Common Lisp (см. ниже).

    Больше по теме:

    Вспомогательные технологии

    Кроме того в CL есть ряд технологий менее значительных, которые нельзя назвать в полной мере уникальными, но которые существенно упрощают его применение и делают программы более ясными, а также дают дополнительные возможности для расширения языка:

    Протокол множественных возвращаемых значений

    Дает возможность возвращать из функции несколько значений и по желанию принимать все их (и привязывать к каким-то переменным) или только часть. По-умолчанию для кода, не использующего эту функциональность, передается только 1-е значение.

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

    Протокол обобщенных переменных

    Это аналог свойств в некоторых ОО-языках. Концептуально, оперирует понятием места (place) — по сути дела ячейки памяти, однако не физической (без манипуляции указателями) — это может быть просто объект или же элемент какой-то структуры (будь-то опять же объект, список, массив и т.д.) Таким образом, имеются намного большие возможности, чем при использовании обычных свойств, поскольку для любой функции, которая читает значения какого-либо места, можно указать функцию которая его значение задает.

    Больше по теме: Paul Graham, On Lisp, Ch.12 “Generalized Variables”

    Макросы чтения

    Это инструмент модификации синтаксиса языка за пределы s-выражений, который дает программисту возможность, используя компилятор Lisp, создать свой собственный синтаксис. Его работа основана на фундаментальном принципе Lisp-систем: разделении времени чтения, времени компиляции и времени выполнения — REPL (Read-Eval-Print Loop). Обычные макросы вычисляются (раскрываются, expand) во время компиляции, и полученный код компилируется вместе с написанным вручную. А вот макросы чтения выполняются еще на этапе обработки программы парсером при обнаружении специальных символов (dispatch characters). Механизм макросов чтения является возможностью получить прямой доступ к Reader’у и влиять на то, как он формирует абстрактное синтаксическое дерево из “сырого” программного кода. Таким образом, можно на поверхности Lisp использовать любой синтаксис, вплоть до, например, C-подобного. Впрочем, Lisp-программисты предпочитают все-таки префиксный унифицированный синтаксис со скобками, а Reader-макросы используют для специфических задач.

    Пример такого использования — буквальный синтаксис для чтения hash-таблиц, который почему-то отсутствует в спецификации языка. Это, кстати, еще один пример того, каким образом CL дает возможность изменить себя и использовать новые базовые синтаксические конструкции наравне с определенными в стандарте. Основывается на буквальном синтаксисе для ассоциативных списков (ALIST):


    ;; a reader syntax for hash tables like alists: #h([:test (test 'eql)] (key . val)*)
    (set-dispatch-macro-character #\# #\h
    (lambda (stream subchar arg)
    (declare (ignore subchar)
    (ignore arg))
    (let* ((sexp (read stream t nil t))
    (test (when (eql (car sexp) :test) (cadr sexp)))
    (kv-pairs (if test (cddr sexp) sexp))
    (table (gensym)))
    `(let ((,table (make-hash-table :test (or ,test 'eql))))
    (mapcar #'(lambda (cons)
    (setf (gethash (car cons) ,table)
    (cdr cons)))
    ',kv-pairs)
    ,table)))))

    Больше по теме: Doug Hoyte, Let Over Lambda, Ch.4 “Read Macros”


    Послесловие

    В заключение хотелось бы коснуться понятия высокоуровневого языка программирования. Оно, конечно, является философским, поэтому выскажу свое мнение на этот счет: по-настоящему высокоуровневый язык должен давать программисту возможность выражать свои мысли, концепции и модели в программном коде напрямую, а не через другие концепции, если только те не являются более общими. Это значит, например, что высокоуровневый язык должен позволять напрямую оперировать такой сущностью, как функция, а не требовать для этого задействовать другие сущности такого же уровня абстракции, скажем, классы. Подход к созданию высокоуровневого языка можно увидеть на примере Common Lisp, в котором для каждой задачи выбирается подходящая концепция, будь то объект, сигнал или место. А что дает нам использование по-настоящему высокоуровневых языков? Большую расширяемость, краткость и адаптируемость программы к изменениям, и, в конце концов, настоящую свободу при программировании!

    Интересная задачка: вытесняющий мультипроцессинг в userland
    Нужно в рамках одной нити управления реализовать поочередно работающие 2 "процесса" (скажем, вычисление 2-х функций), переключение между которыми происходит по регулярному сигналу таймера. Естественно, что при переключении состояние вычисления должно сохраняться и восстанавливаться на следующем такте (а не начинаться заново каждый раз).

    Интересно было бы увидеть, как это реализовывается в разных языках? (Я так понимаю, что в Smalltalk это должно быть тривиально за счет наличия объекта контекста. А где еще?)
    SBCL 1.0.40

    Обновление SBCL 1.0.40 в lisp-репизотрии для Ubuntu и Debian. Из приятного - тесты у него проходят теперь и на 32-битной платформе. Внутри у него ASDF2 и теперь нет необходимости в пакете asdf-binary-locations.

    sbcl-1.0.40 под win32
    http://dl.dropbox.com/u/5521262/sbcl-1.0.40-x86-windows-binary.msi
    В ожидании...
    Итак, слухи потвердились, Google купил таки ITA за 700 млн. долларов: http://www.google.com/press/ita/. Поскольку для ITA ключевой технологией является Common Lisp, то встаёт интересный вопрос - какое влияние эта сделка окажет на дальнейшее развитие Common Lisp?
    Новый Лисп стартап
    Недавно Канадский Лисп-программист Уоррен Уилкинсон объявил о своем новом проекте - FormLis. FormLis состоит из комбинации вики и системы генерации веб-форм из простейшей разметки (пример), а так же безсхемной базы данных поддерживающей генерированные формы.

    Уоррен решил описать конструкцию и использование встроенного Форт (Forth) компилятора в Лиспе (англ.). Интересно что Дуг Хойт посвятил главу своей книги Let Over Lambda конструкции встроенного Форт компилятора, но увидеть такое в веб-приложении неожиданно и оригинально.
    Продолжение виндолиспонитей

    Пришлось серьезным образом переделать обработку прерываний в sbcl/win32, в очередной раз переписать остановку и прерывание нитей. После этого наконец-то успешно проходят тесты, не проходившие ранее. В связи с чем успешное разрешение ситуации с тредами видится все ближе, но объем изменений становится все больше и больше, а уверенность в быстром вливании изменений в основную ветку все уменьшается. Но в качестве отдельного форка изменения вполне поддерживаемые.

    Код пока не выложен, так как он требует некоторой доработки и чуть большего тестирования.

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

    ASDF является фактически единственным на данный момент сборщиком Common Lisp програм. Более того он играет свою роль в различных менеджерах дистрибутивов, хотя и не является самим по себе полноценным решением. И, я бы сказал, что со своей ролью инструмента сборки он справляется довольно неплохо, а вот в сфере описания дистрибутивов есть проблемы, которые пока что не решены на практике. Это заставляет многих людей, в том числе и меня, вообще не пользоваться подобными инструментами и произвоить установку дистрибутивов вручную (благо в Common Lisp среде, в том числе и благоаря ASDF это очень просто1), а других (в том числе и меня) задумыватся о создании собственного средства: примеры тому — Mudballs, Lispy, CL-Librarian, LibCL...

    В недавней статье в журнале ПФП я написал, что
    развитие средства управления пакетами должно идти именно с учетом децентрализованной структуры Лисп-среды, а не вопреки ей.

    К такому же заключению мы пришли и в процессе обсуждения темы на форуме lisper.ru, которое также заставило меня немного заглянуть под капот ASDF, чтобы узнать, насколько реально и легко создать на его основе пакетный менеджер. Этот "быстрый взгляд" в итоге вылился в растянутое на целый месяц неспешное ковыряние кода и его доработку для полноценной поддержки версионирования. Свое решение я направил в список рассылки asdf-devel, однако оно вряд ли будет интегрированно. В любом случае, этой теме я собираюсь посвятить отдельную запись в этой серии.

    А начнем мы с того, что нового нам несет ASDF 2, релиз которого состоялся 31 мая, и который уже скоро войдет в вашу любимую Лисп реализацию.

    Целая новая версия системы претерпела существенный рефакторинг и доработку силами Фарэ и Роберта Голдмана. С точки зрения пользователей она включает в себя следующие улучшения (разумеется, накопленные и отлаженные за посление ряд релизов):
    • версионность самой библиотеки, возможность обновления ASDF

    • добавление более простого пользовательского интерфейса

    • добавление нового способа конфигурации

    • сохранение FASL-файлов отдельно (в других директориях) от исходного кода

    • улучшение работы c путями и соответствующее расширение классов component и system

    • исправление некоторых багов


    Вот lightning talk Роберта на встрече TwinCity лисперов, посвященный ASDF 2. Нужно заметить, что ценность этого релиза не только практическая, но и символическая, знаменующая переход проекта на новую стадию разработки: более структурированную и прогнозируемую.

    Итак, кратко о новых фичах.

    1. Возможность обновления ASDF.
    Сейчас любой пользователь может воспользоваться самой новой версией ASDF, просто загрузив ее через (asdf:oos 'asdf:load-op 'asdf) (но не через require!) Подробнее об этом в мануале, который, также улучшается.

    2. Более простой пользовательский интерфейс — это функции load-system, compile-system и test-system. Вроде бы как, тривиальное изменение, но избавляющее новичков от необхоимости думать, почему это операции load-op и т.п. являются объектами, а не функциями, и других схожих волнений. Это важно в борьбе с кажущейся сложностью ASDF. При этом методы на test-op, как и раньше — и это понятно — нужно описывать разработчикам систем.

    3. Системный реестр (source-registry) — новый способ конфигурации :)
    Именно таким является новый, и по замыслу авторов основной способ задания места расположения исходников систем у пользователя. В то же время старый вариант через *central-registry* остается полностью функциональным и поддерживаемым. Более того, он был дополнен проверкой на самую неприятную и, наверно, частую ошибку при использовании ASDF — отсутствие слеша в конце пути к директориям — теперь этой неразберихи больше не будет.

    Что же такое системный реестр? Это набор конфигурационных файлов в предопределенной структуре директорий для каждого пользователя, смоделированных по принципу *.conf.d директорий в Unix. А также собственно DSL для конфигурации. Простой пример того, как это работает из мануала:
    В директории ~/.config/common-lisp/ находится файл source-registry.conf со следующей конфигурацией:

    (:source-registry
    (:tree "/home/fare/cl/")
    :inherit-configuration)

    В данном случае поиск установленных систем производится рекурсивно в поддиректориях в /home/fare/cl/.


    Однако это объяснение и пример далеко не исчерпывающи, поэтому лучше читать соответствующий раздел руководства и проверить все собственноручно.

    Честно говоря, как по мне, то новый подход для индивидуального разработчика менее удобен, чем использование *central-registry*. Однако он лучше подойдет для средств автоматического конфигурирования (и, я думаю, что как раз опыт в рамках ITA, где используется много Lisp серверов приложений, послужил отправной точкой для разработки этого способа), а также для использования в пакетных менеджерах. И хорошо, что теперь есть альтернативы для каждого из случаев.

    4. Cохранение FASL-файлов теперь происходит в компилятор-специфичных директориях, по умолчанию спрятанных в домашней диретории пользователя. Благодаря этому устраняются проблемы как конфликта прав в случае использования одних и тех же исходников библиотек разными пользователями, так и stale FASLs, которые возникают при апгрейде реализации (в частности для SBCL).

    5. В общем, исправлены основные недочеты, которые присутствовали в ASDF при работе с путями в разных операционках, а в классы компонент и система добавлены слоты, указывающие абсолютное положение их в файловой системе. Также исправлены и некоторые другие баги, о чем можно почитать в Changelog'е.

    Какие проблемы остались? Из существенных для меня — две: нечеткая семантика форсированных операций (параметр :force t), а также недостатки работы с версиями (этой теме будет посвящена отдельная запись, поэтому не буду касаться ее здесь).

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

    В следующей записи — немного о внутренностях ASDF...




    1 Мой алгоритм установки Lisp библиотеки:
    - Загрузить tar.gz файл
    - Развернуть в ~/lib/lisp/
    - Создать символическую ссылку на ASD-файл в ~/.lisp/
    - (И вариация для случая работы с разными версиями одного пакета): ссылка на ASD-файл основной версии в ~/.lisp/, а при необходимости использования альтернативной версии, скажем hunchentoot-0.15.7 вместо hunchentoot-1.1.0 (push "~/lib/lisp/hunchentoot-0.15.7/" asdf:*central-registry*) (аналог PATH).
    Common Lisp на миллиард
    Самой известной организацией, использующей Common Lisp для разработки коммерческих систем, является, вероятно, ITA Software и кажется это единственная организация, озабоченная проблемами разработки крупномасштабного ПО c помощью Common Lisp (XCVB родилась, как раз, ради нужд этой организации). До не давнего времени я был совершенно не в курсе о размере этой организации, но тут с некоторого времени стали курсировать слухи, что Google пытается её купить и речь идёт о сумме, близкой к одному миллиарду долларов. Мне кажется, что это достаточно крупная сумма для того, что бы навсегда закрыть вопрос о возможности/целесообразности коммерческого применения Common Lisp...
    archimag-lisp-overlay
    Сегодня с моим форком gentoo-lisp-overlay, который базировался на github.com, случилось несчастье - он отказался push-ится, мотивируя это тем, что мол нет доступа к ./objects, как будто это и не мой репозиторий вовсе. В поддержке gitnub есть зарегистрированное подобное сообщение, но там человека сразу послали, мол это не проблемы github. Интересно, а чьи это проблемы, если проекты пушился-пушился и вдруг перестал? Потыкавшись, решил попробовать удалить его и создать заново - но создаваться заново с таким же именем он не захотел, мотивируя это уже совсем чем-то странным. Так что, пришлось окончательно его грохнуть и создать новый archimag-lisp-overlay, куда всё и залил. Вроде поломанные при этом ссылки (таких было несколько) починил и теперь всё должно быть как прежде, но только в немного другом месте.
    Обновление REPO
    Обновление REPO
    Обновился пакеты в lisp-репизотрии для Ubuntu и Debian.


    • SLIME (Superior Lisp Interaction Mode for Emacs) - 20100624;
    • bordeaux-threads - 0.7.0
    Про ассемблерные вставки

    На lisper.ru началась дискуссия про ассемблерные вставки в SBCL, вот краткое описание того, как их можно делать

    В SBCL (как и многих других компиляторах) существуют т.н. примитивы - языковые конструкции, не сводимые к другим. В SBCL такие примитивы реализуются посредством преобразования на уровнях IR1 (на котором код представлен в виде графа потока управления/данных) и на уровне IR2 (уровень виртуальной машины). На уровне IR1 реализуются такие конструкции, как let, lambda, а более простые вещи вроде "встроенных функций" (арифметика, выделение памяти и прочее) - в виде инструкций виртуальной машины, с которыми связаны процедуры генерации машинного кода.

    Эти инструкции называются VOP'ами, и SBCL так устроен, что набор доступных инструкций (VOP'ов) не фиксирован, и их можно пополнять. Например, в проекте sb-cga (computer graphics algebra) путем добавления нескольких VOP'ов реализовано быстрое умножение матриц и векторов на SSE2.

    Рассмотрим простейший пример - добавим ассемблерную вставку, которая делает XOR двух чисел.

    Абстрактно, код ассемблерной вставки выглядит (в интеловском синтаксисе) так:

    mov result, arg1
    xor result, arg2
    

    (здесь result - имя регистра, в который надо записать результат, arg1 и arg2 - регистры, в которых лежат аргументы).

    Чтобы вставить этот код в код на лиспе, надо сделать две вещи:

    1. Объявить новую примитивную функцию (назовем ее my-xor)
    2. Добавить новую VOP, соответствующую этой примитивной функции
    3. Определить функцию (на этот раз обычную функцию, а не примитив языка) my-xor

    Проделав первые две операции, SBCL научится компилировать выражения вида (my-xor a b).

    За определение примитива отвечает макрос SB-C:DEFKNOWN, в который передаются типы аргументов и возвращаемых значений функции. В нашем примере будем использовать только короткие числа - fixnum.

    
    (sb-c:defknown my-xor (fixnum fixnum) fixnum)
    

    Добавить новую VOP можно макросом SB-C:DEFINE-VOP. У этого макроса есть много параметров, касающихся кодогенерации (выбор регистров, типы аргументов, временные регистры, время жизни различных значений), оптимизации ("стоимость" операции), и других. Для заданного примера определение VOP выглядит следующим образом:

    
    (sb-c:define-vop (my-xor)
      (:translate my-xor)
      (:args (a :scs (sb-vm::any-reg))
             (b :scs (sb-vm::any-reg)))
      (:arg-types fixnum fixnum)
      (:results (c :scs (sb-vm::any-reg)))
      (:result-types fixnum)
      (:policy :fast-safe)
      (:generator
       0
       (sb-c::inst sb-vm::mov c a)
       (sb-c::inst sb-vm::xor c b)))
    

    Вкратце разберем различные части этого объявления

    • (sb-c:define-vop (my-xor) задает имя VOP'а. В принципе, имя VOP'а может не совпадать с именем функции, и часто бывает так, что одной функции соответствуют несколько VOP'ов (например, различные варианты реализации функции +)
    • (:translate my-xor) указывает, что определяемый VOP реализует примитив my-xor. Эта часть определения указывает, что для компиляции выражения (my-xor a b) следует использовать этот VOP.
    • (:args ...) и (:results ...) описывает имена и то, как подаются аргументы и сохраняются результаты в VOP. :SCs задает список допустимых storage-class'ов (можно считать, что storage-class - это либо некоторое множестве регистров, либо стек, либо стек FPU). Класс sb-vm::any-reg сооветствует любому регистру общего назначения.
    • (:arg-types ..) и (:result-types ...) задает типы аргументов, к которым применим VOP и тип возвращаемого значения.
    • (:policy :fast-safe) определяет, когда следует вызывать этот VOP (если имеется выбор среди доступных VOP'ов): :small - когда нужен маленький код, :fast - когда нужен быстрый код, :safe - безопасный код, и :fast-safe - быстрый и безопасный.
    • (:generator 0 ...) задает собственно процедуру генерации машинного кода. Внутри этой процедуры доступен макрос SB-C::INST, реализующий интелоподобный синтаксис ассемблера. Аргументы для инструкций можно задавать: числовыми константами, ссылками на аргументы, результат и временные переменные (в примере это используется), ссылками на конкретные регистры (например, SB-VM::EAX-TN - ссылка на регистр EAX), ссылками на адрес памяти (например, (sb-vm::make-ea :dword :base sb-vm::eax-tn :index sb-vm::edi-tn :scale 2 :disp 3) - это ссылка 4хбайтное слово по адресу eax + edi * 2 + 3), ссылками на метки (которые либо явно создаются с помощью gen-label и emit-label: (let ((l (gen-label)) (inst jmp l) (emit-label l)) - это полезно в макросах, используемых в теле VOP или же в теле генератора ставится символ: (:generator 0 (inst jmp label-1) label-1 )) и ссылками на какие-то объекты (например, (sb-vm::make-fixup "strlen" :foreign) - ссылка на внешнюю функцию strlen). При этом, в качестве процедуры генерации может быть произвольный код на лиспе.

    После этого, для полноты надо сделать еще одну маленькую вещь - объявить функцию-обертку над примитивом my-xor, чтобы можно было не только компилировать выражения вида (my-xor a b), но и иметь функцию #'MY-XOR, которую можно передать, например, в map:

    
    (defun my-xor (a b)
      (my-xor a b))
    

    На первый взгляд, определение функции my-xor - это бред: функция определена как рекурсивная функция, но без базы рекурсии и вообще без шага рекурсии. Но на самом деле тут определяется функция my-xor не через функцию my-xor, а через примитив my-xor, поэтому компилятор генерирует не вызов функции, а подставляет VOP, соответствующий примитиву.

    Убедимся в этом:

    (disassemble 'my-xor)
    =>
    
    ; disassembly for MY-XOR
    ; 02DED843:       488BD1           MOV RDX, RCX               ; no-arg-parsing entry point
    ;       46:       4831FA           XOR RDX, RDI               ; <-- вот наш код
    ;       49:       488BE5           MOV RSP, RBP
    ;       4C:       F8               CLC
    ;       4D:       5D               POP RBP
    ;       4E:       C3               RET
    ;       4F:       CC0A             BREAK 10                   ; error trap
    ;       51:       02               BYTE #X02
    ;       52:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
    ;       53:       54               BYTE #X54                  ; RCX
    ;       54:       CC0A             BREAK 10                   ; error trap
    ;       56:       02               BYTE #X02
    ;       57:       08               BYTE #X08                  ; OBJECT-NOT-FIXNUM-ERROR
    ;       58:       95               BYTE #X95                  ; RDX
    ;       59:       CC0A             BREAK 10                   ; error trap
    ;       5B:       04               BYTE #X04
    ;       5C:       08               BYTE #X08                  ; OBJECT-NOT-FIXNUM-ERROR
    ;       5D:       FED501           BYTE #XFE, #XD5, #X01      ; RDI
    

    На данном листинге 2 инструкции по адресу 0x02DED843 - это созданный только что VOP, а остальное - стандартный эпилог функции и код по проверке корректности аргументов функции.

    Теперь воспользуемся только что созданным примитивом для определения новой функции:

    (defun foo (a b)
      (declare (type fixnum a b)
               (optimize (speed 3) (safety 0)))
      (my-xor (1+ a) (1+ b)))

    И посмотрим на скомпилированный код этой функции:

    
    ; disassembly for FOO
    ; 0325A727:       48FFC2           INC RDX                    ; no-arg-parsing entry point
    ;       2A:       48FFC7           INC RDI
    ;       2D:       48C1E203         SHL RDX, 3
    ;       31:       48C1E703         SHL RDI, 3
    ;       35:       488BD2           MOV RDX, RDX               ; <--
    ;       38:       4831FA           XOR RDX, RDI               ; <-- код VOP'а my-xor
    ;       3B:       488BE5           MOV RSP, RBP
    ;       3E:       F8               CLC
    ;       3F:       5D               POP RBP
    ;       40:       C3               RET

    Из дизассемблированного кода видно, что был подставлен именно тот код, который мы хотели.

    Ссылка на полученный код: http://lisper.ru/apps/format/138.

    Код получается многословным, в первую очередь, из-за того, что тут используется полная функциональность компилятора, а не просто вставка ассемблерного кода.

    *notitle*
    На lisper.ru недавно спрашивали, можно ли в программах на лиспе делать ассемблерные вставки.

    На лиспе можно не просто делать ассемблерные вставки, на лиспе можно писать прямо в машкодах!:)

    
    (deftype vector4 () '(simple-array single-float (4)))
    (deftype simple-ub8-vector () '(simple-array (unsigned-byte 8) (*)))
    
    (defmacro with-pinned-vectors ((&rest specs) &body body)
      (if (null specs)
        `(progn ,@body)
        `(cffi:with-pointer-to-vector-data ,(first specs)
           (with-pinned-vectors ,(rest specs) ,@body))))
    
    (defvar *function* (coerce
                         #(#x8B #x44 #x24 #x08
                           #x0F #x10 #x00
                           #x8B #x44 #x24 #x0C
                           #x0F #x10 #x08
                           #x0F #x58 #xC1
                           #x8B #x44 #x24 #x04
                           #x0F #x11 #x00
                           #xC3)
                         'simple-ub8-vector))
    
    (defun vector4-add (out v1 v2)
      (declare (type vector4 out v1 v2))
      (with-pinned-vectors ((function *function*)
                            (p-out out)
                            (p-v1  v1)
                            (p-v2  v2))
        (cffi:foreign-funcall-pointer
          function (:convention :cdecl)
          :pointer p-out
          :pointer p-v1
          :pointer p-v2
          :void))
      out)
    



    P.S. Данная функция(vector4-add) адекватно работает только на реализациях, позволяющих получить из векторов типа (simple-array single-float (*)) и (simple-array (unsigned-byte 8) (*)) указатель на их данные.
    P.P.S. 32-bit x86 + SSE

    btw:
    http://tkpapp.blogspot.com/2010/05/upgraded-array-element-types-and-pinned.html
    ICFPC-2010
    В этом году я второй раз участвовал в ICFP Contest (отчёт об участии в ICFPC-2009 ). Ожидалось заранее, что будет какое-нибудь устройство, которым можно будет управлять. Но ожидания слегка не оправдались. :) В этот раз задача походила на сказку - пойди туда не знаю куда, сделай то не знаю что. Участвовал в команде Skobochka и набрали где-то 3 с чем-то балла. Но обо всём по порядку:

    День1
    Прямо к четырём оказаться для контеста у меня не получилось, поэтому я опоздал где-то на полчаса. Начал читать задание. Кто-то в это время уже набросал в CLOS модель машин и разгадывал фабрику. Фабрика представляла собой гейты с двумя входами (левый и правый) и двумя выходами (также левый и правый) и соединениями между ними. При этом соединение к одной точке (вход или выход) могло производиться только одной линией. Также фабрика содержит специальный гейт, называемый внешним, от которого поступает информация и на который эта информация уходит. В чате все предположили, что слева - входы гейта, справа - выходы. Ну и описание соответственно. Приведу прмер фабрики, которая была в задании:

    19L:
    12R13R0#1R12R,
    14R0L0#4R9L,
    9R10R0#3L8L,
    2L17R0#5L9R,
    15R1L0#10R13R,
    3L18R0#6L15L,
    5L11R0#13L12L,
    19R16R0#11R8R,
    2R7R0#11L10L,
    1R3R0#18L2L,
    8R4L0#16L2R,
    8L7L0#15R6R,
    6R0R0#14L0L,
    6L4R0#14R0R,
    12L13L0#17L1L,
    5R11L0#16R4L,
    10L15L0#17R7R,
    14L16L0#18R3R,
    9L17L0#19R5R,
    X18L0#X7L:
    19L
    К этой фабрике необходимо было подать на вход последовательность [0,2,2,2,2,2,2,0,2,1,0,1,1,0,0,1,1] и на выходе бы получился ключ, который должна выдавать каждая из фабрик.
    Посмотрев, как входы и выходы специфицируются на каждом гейте и как вход на цепь специфицируется входами гейтов я сразу же подумал подать пустую фабрику "X: :X" и получил последовательность. И тут, как говорится, "Остапа понесло". Я предположил, что незвестна не только функция гейта, но ещё и функция внешнего гейта, на котором также происходит преобразование. Весь оставшийся день прошёл в попытках решить аналитически две неизвестные функции гейтов.

    День2
    День второй продолжился тем же, чем и закончился, а другие команды уже набрали свои первые очки. После обеда я всё же решил предположить, что внешний гейт - это всего лишь соединение машины и никакого преобразования там не происходит. Подавая разные топологии фабрик я всё же получил функцию активации гейта. На этом я почувствовал себя обессиленным. Наши же в это время создали создавалку случайных фабрик и сабмитилку прямо на сервер. И как бы было не смешно, но это принесло нам первые очки. Созданная симуляция фабрик не работала, дружно обсуждали семантику обратных связей, но так внятно ничего сделать уже не смогли. Перебиралка засабмитила решения ещё для двух машин. Пытались найти формат машин, но как-то безуспешно. К концу дня почти все сдались и перешли в разряд наблюдателей. К этому времени остальные команды закончили аналитику и дописали своих ботов. Сервер лёг.

    День3
    Состязание завершилось. 3 балла.

    Итог
    В следующем году обязательно буду участвовать. Всей команде Skobochka (allchemist, vseloved, rigidus, treep, dmitry_vk, ...) спасибо.
    REPL рулит и педалит
    Запустил обмолачиваться 13.5 гигабайт данных в самописном лисповом утиле. Посмотрел на скорость, опечалился. Приостановил утиль (C-c, выпадает в дебаггер с repl'ом), поставил ограничение на примерно половину длины дампа, пустил работать дальше. Открыл вторую консоль, запустил такой же утиль, только работающий со второй половиной. Типа, решил на ходу заюзать SMP, не теряя результатов из уже обработанных данных :)

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

    Утром ноут проснулся нормально, утили молотят дальше, но до чего же эта лисповая интерактивность прикольная!
    trivial-xmpp 0.1 release

    Использую в одном своем проекте обмен по протоколу XMPP, решил выделить это дело в отдельную библиотечку, trivial-xmpp, может кому-то еще пригодится. Пробовал CL-XMPP, не очень понравилось - зависимостей много и прочих мне не нужных наворотов.

    Пришлось включить в библиотеку модифицированные версии XMLS и CL-SASL. CL-SASL избавлена от зависимости от IRONCLAD, из-за 2-х функций тянуть такого монстра - ужос! Лёгонький MD5 справляется прекрасно.

    Зависимости: MD5, CL-BASE64, CL+SSL.

    Исходный код: svn co svn://gzip4.pp.ru/proj/trivial-xmpp/tags/rel-0.1

    Простейший пример использования:

    (defun jabber-bot ()
      (let* ((socket (usocket:socket-connect "xmpp.ru" 5222))
             (stream (usocket:socket-stream socket)))
        (unwind-protect
             (progn
               (setf stream (xmpp:xmpp-connect stream "trivial-xmpp" "xmpp.ru"
                                               "secreet" :resource "jabber-bot"))
               (loop for message = (xmpp:parse stream)
                  while message
                  for to = (loop for a in (xmpp:node-attrs message)
                              when (string= (first a) "from") return (second a))
                  do (xmpp:send (xmpp:message to "hello world" :type :chat
                                              :subject "jabber-bot") stream))
               (close stream))
          (when socket (usocket:socket-close socket)))))
    
    Коммерческий лисп
    Я теперь счастливый пользователь 64-битного энтерпрайзного Лиспворкса.
    IDE у LW мощный, всё есть, и работает из коробки. Меня обилие иконок и менюшек повергло в глубокий шок, убежал обратно в Emacs + Slime. К тому же, окошки этого IDE не очень хорошо работают в StumpWM. Но так, конечно, чётко, что для Лиспа есть такой сурьозненький продукт, с мануалами, профайлером, дебаггером, браузером классов, рисовалкой графов вызовов, клепалкой ui-форм, etc.

    Что в коммерческих лиспах плохо, так это поддержка в opensource'ных библиотеках. С утра наткнулся на два бага в таком мелком пакете, как trivial-garbage. Мой cl-zmq тоже нормально не загрузился: lw пожаловался на символ identity, которому справедливо надо делать шэдоу. Странно, три других лиспа (sbcl, clisp и clozure) на это внимания не обращали...
    Продолжение дискуссии: дизайн
    Собственно, продолжаем обсуждение проблем разработки программного обеспечения. Тут придётся немного апеллировать к утверждениям из поста thesz, так что прощу прощения за перепечатки.
    Если программа может быть написана двумя или более способами, то необходимо произвести выбор одного из них. Archimag предлагает выбрать дизайн наугад и потом оценить, подошёл ли он.
    Во-первых, любая программа может быть написана бесконечным количеством способом. Т.е. ситуация, когда имеется только один вариант дизайна не бывает в принципе. Во-вторых, я никогда не действую наугад и, естественно, не рекомендую это делать другим.
    Желательно бы делать лучше, например, уметь сравнивать дизайны между собой.
    В том самом обсуждении я предложил следующий вариант: сперва сравниваем дизайны по тому, как много сценариев программы они покрывают, потом сравниваем по количеству кода, потребному для реализации дизайна. Такая несложная иерархия.

    Если включить в рассмотрение важность сценариев и сложность их реализации и отсортировать их, как это рекомендует gaperton, то получится хорошая система оценки дизайна программы, ещё более иерархичная, быстрее отсеивающая относительно плохие дизайны.
    Слава роботам! Так действуют машины, когда им надо решить какую-нибудь более-менее сложную задачу. Т.е. в идеале нужно перебрать все варианты и из них выбрать самый подходящий. Когда вариантов бесконечно много (как в случае с дизайном) надо действовать чуть хитрее. Поиск в пространстве состояний сейчас достаточно хорошо проработанная область, но она содержит алгоритмы для машин, не для людей. Точнее, мы используем эти алгоритмы для того, что бы научить машину делать нечто, для чего ранее требовался человеческий интеллект. Возможно, когда-нибудь программы будут писать роботы, который действительно будут действовать схожим образом. Хотя тоже сомнительно, ибо простой перебор вариантов не работает даже в шахматах, которые хоть и являются довольно сложной проблемой, но довольно ограниченной и, мало того, конечной. В целом, я считаю предложенный способ сравнения, по крайней мере, наивным.

    Человек действует не так. Мы не может позволить себе писать программу несколько раз и выбирать лучший вариант (впрочем, подобный процесс происходит на более высоком уровне: разные команды пишут схожие продукты, среди которых практика использование отбирает лучшие.).
    типы языка Хаскель основываются на логике, по-моему, на классическом её варианте (могу быть неправ, но в прилагательном;). Выражая типы компонент и пробуя прикинуть приблизительную их реализацию Хаскеле мы проверяем наш дизайн с помощью системы автоматического доказательства теорем. Это помогает отсевать те варианты дизайна, что не могут быть логически стройно сформулированы.
    Слава роботам! Или не слава... Любое доказательство теорем основывается на других теоремах и аксиомах. Если нижележащие теоремы/аксиомы неверны, то любое основанное на них доказательство таковым не является и вообще ничего не значит, т.е. не имеет никакой ценности. При разработке программного обеспечения, в большинстве случаев (за исключением некоторых, достаточно редких в повседневной практике областей) мы не обладаем полнотой информации о создаваемой системе, а значит просто не можем физически положить в основу корректный набор аксиом (и теорем).

    Если рассуждать о дизайне, то считаю необходимым отдельно рассматривать зачем он нужен, какими средствами реализуется и в чём заключается процесс его разработки.

    Цели

    Я считаю, что разработка дизайна преследует следующие цели (необходимость удовлетворения требованиям задачи я считаю само-самой разумеющимся и отдельного рассмотрения не требующим, в конце концов - это цель всей программы, а не одного дизайна):
    • Уменьшение сложности
    • Устранение дублирования кода
    • Повышение гибкости, готовность к изменениям
    Кстати, с этих позиций довольно просто обосновать, например, опасность перегрузки операторов в С++, ибо с точки зрения дизайна она только повышает сложность и больше ничего.

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

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

    Инструмент

    Основным инструментов для работы над дизайном является декомпозиция. Если говорить в целом, то моя позиция заключается в том, что цель декомпозиции найти "хорошие простые ответы на правильные вопросы", при чём, как обычно "правильные вопросы" это больше половины дела. Применительно к языкам программирования имеет смысл выделять какие средства предоставляет язык для отображения выбранного способа декомпозиции в коде. Для Common Lisp я выделяю следующие возможности, которые определяют дизайн системы:
    • Функции (включая замыкания)
    • Структуры данных (структуры, классы, различные виды списков и т.д.)
    • Generic-фукнции (которые следует рассматривать отдельно и от функций, и от классов)
    • Макросы
    • Динамические переменные
    • Рестарты
    • Пакеты
    Это довольно много (по сравнению с большинством других языков) и с одной стороны предоставляет весьма мощный набор инструментов, которые могу облегчить процесс создания качественного дизайна, а с другой такое богатство может завести разработчика в дебри. Кстати, проблема многообразия средств также характерна и для С++, который заслужил благодаря этому довольно дурную славу, но это скорей не проблема языка, а проблема уровня самодисциплины у разработчиков. В Common Lisp же эта проблема выражена ещё сильнее и разработчик должен постоянно контролировать свое желание "заюзать ещё пару крутых фич".

    Кстати, насчёт "хороших простых ответов на правильные вопросы" хочу привести однин, как мне кажется, вполне уместный исторический пример: Коперник vs Птолемей. С точки зрения математики, особой разницы между подходами Коперника и Птолемея нет, они оба вполне корректны, мало того, опять же с точки зрения математики последователи Птолемея делали совершенно потрясающую вещь - раскладывали орбиты планет в ряды Фурье и это за 1000 лет до появления математического анализа! Коперник же задал правильный вопрос (что вокруг чего вращается?) и дал на него простой ответ, в результате смог объяснить наблюдаемое движение небесных тел с помощь значительно более простого математического аппарата.

    Процесс
    Определившись с целями и доступным инструментом можно попробовать разобраться в процессе создания дизайна. Если рассматривать вопросы декомпозиции, то думаю все согласятся, что "правильная декомпозиция с первого раза" для достаточно сложных проектов может является замечательным примером человеческого гения, периодически освещающего наш мир. В большинстве же случаев необходим достаточно длительный поиск подходящего решения, основанный на интуиции, способности предвидеть и понимании создаваемой системы. Ключевым фактором является склонность человека к ошибкам, а также замечательная способность мозга создавать иллюзию того, что мы обладаем полнотой информации. Единственным известным мне действенным способ проверки правильности принятых решений является непосредственное написание кода (который можно реально запустить), во время которого выявляются все не-стыковки и противоречия в дизайне. Одним из, уже можно считать, традиционных и устоявшихся способов проверки дизайна является написание unit-тестов: хотя разработка программы может находиться ещё только в самой ранней стадии, благодаря им мы получаем реальный код, который можно запустить и проверить принятые решения. Другим, ещё более заслуженным способом является создание прототипов. Common Lisp предоставляет ещё одну блестящую возможность, которая может быть эффективно использована для работы над дизайном - REPL. Тут я хочу отдельно отметить, что под REPL я понимаю не только командную строку (которая есть сейчас для многих динамических языков: Python, Ruby, JavaScript и т.п.), но целый комплекс средств, предоставляемых SLIME или IDE коммерческих реализаций, которые значительно упрощают интерактивную разработку (например, простая перекомпиляция отдельной функции или класса непосредственно во время работы над исходным кодом, наглядное раскрытие макросов и т.п.). Очень простая возможность проверки решений буквально подталкивает разработчика к большому количеству экспериментов с исходным кодом, что создаёт все предпосылки для создания качественного дизайна. При этом, весьма важную роль играет динамическая типизация, которая позволяет нарушать общую корректность программы ради проверки какой-либо идеи. В языках со статической проверкой типов внесение изменений ради эксперимента может оказаться болезненным, что препятствует эффективному поиску (обычно люди избегают боли).

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

    Common Lisp: Введение в Условия и Перезапуск (перевод)
    Оригинал: Common Lisp: A Tutorial on Conditions and Restarts

     

    Содержание

     

    Предисловие

    Common Lisp's condition system, with its exceptions and restarts, is one of its unique features. Unfortunately, there aren't many good tutorials explaining this concept very well. One good introduction to this is the chapter on conditions and restarts in Peter Seibel's excellent book, Practical Common Lisp (see 1, 2). This tutorial assumes some knowledge of the condition system, so you might want to read that chapter before proceeding.

    Система условий, с её исключениями и перезапусками, является одной из уникальных особенностей языка Common Lisp (CL). К сожалению, учебных материалов, объясняющих данную концепцию, существует не так уж много. Информацию, касающуюся условий и перезапусков, можно найти в одной из глав прекрасной книги Питера Сибела (Peter Seibel), "Практический Коммон Лисп" (Practical Common Lisp) (см. 1, 2). Настоящее введение предполагает наличие некоторых знаний касательно системы условий, поэтому перед тем как продолжить, необходимо как минимум прочитать эту главу (русский перевод).

    I'll attempt to show how effective CL's condition system can be, witha validator for CSV (comma-separated values) files. The validator willcheck that all the fields in each row of the file are valid (accordingto some defined criteria).

    Я попытаюсь показать, насколько может быть эффективна система условий в CL на примере валидатора для CSV-файлов. Этот валидатор будет проверять правильность (в соответствии с некоторыми заданными критериями) всех полей данных во всех строках файла.

    The first row of the CSV will be a comma-separated list of headers, followed by rows with each column corresponding to the headers in the first row. A sample file looks like this:

    Первая строка CSV-файла - разделяемый запятыми список заголовков, за которым следуют строки, в которых каждая колонка соответствует заголовку из первой строки. Пример может выглядеть следующим образом:

    rating,url,visitors,date
    4,http://chaitanyagupta.com/home,1233445,2000-01-01
    5,http://chaitanyagupta.com/blog,33333,2006-02-02
    5,http://chaitanyagupta.com/code,2121212,2007-03-03
    

     

    Валидация заголовков

    First we write functions to validate the four headers we used above: rating, url, visitors, and date. Note that these functions depend on the CL-PPCRE package (http://weitz.de/cl-ppcre).

    Сначала напишем функции для валидации четырех заголовков, приведенных выше: rating, url, visitors и date. Заметьте, что эти функции зависят от библиотеки CL-PPCRE.

    (defun validate-url (string)
      "The URL of the page; should start with http:// or https://."
      (unless (cl-ppcre:scan "^https?://" string)
        (csv-error "URL invalid." :value string)))
    
    (defun validate-rating (string)
      "String should contain an integer between 1 and 5, inclusive."
      (let ((rating (parse-integer string :junk-allowed t)))
        (unless (and (integerp rating) (<= 1 rating 5))
          (csv-error "Rating not an integer in range." :value string))))
    
    (defun validate-visitors (string)
      "The number of visitors to the page; string should contain aninteger
    more than or equal to zero."
      (let ((visitors (parse-integer string :junk-allowed nil)))
        (unless (and (integerp visitors) (>= visitors 0))
          (csv-error "Number of visitors invalid." :value string))))
    
    (defun validate-date (string)
      "The published date of the URL. Should be in yyyy-mm-dd format."
      (let ((split (cl-ppcre:split "-" string)))
        (flet ((!valid-number-of-digits-p (string n)   ; See note 3
                 (and (every #'digit-char-p string)
                      (= (length string) n))))
          (unless (and (!valid-number-of-digits-p (first split) 4)
                       (!valid-number-of-digits-p (second split) 2)
                       (!valid-number-of-digits-p (third split) 2))
            (csv-error "Published date not in valid format." :value string)))))
    

    All these functions take a string as an argument, and if it doesn't satisfy the validation criteria, an error is signalled using the function csv-error. This is defined next.

    Все эти функции принимают в качестве аргумента строку, и если строка не удовлетворяет критерию валидации, они сигнализируют ошибку, вызывая функцию csv-error, определение которой приведено далее.

     

    Сигнализируем об ошибках валидации.

    The function csv-error signals a condition of type csv-error, both of which are defined below.

    Функция csv-error сигнализирует условие, имеющее тип csv-error. Посмотрим на их определения:

    (define-condition csv-error (error)
      ((message
        :initarg :message
        :accessor csv-error-message
        :initform nil
        :documentation
        "Text message indicating what went wrong with the validation.")
       (value
        :initarg :value
        :accessor csv-error-value
        :initform nil
        :documentation
        "The value of the field for which the error is signalled.")
       (line-number
        :initarg :line-number
        :accessor csv-error-line-number
        :initform nil
        :documentation
        "The line number of the row in for the error was signalled.")))
    
    ;; Do something more useful than the default printer behaviour
    (defmethod print-object ((object csv-error) stream)
      (print-unreadable-object (object stream :type t :identity t)
        (format stream "~@[L~A ~]~S~@[: ~S~]"
                (csv-error-line-number object)
                (csv-error-message object)
                (csv-error-value object))))
    
    ;; We use this function to signal our validation error
    (defun csv-error (message &key value line-number)
      (error 'csv-error
             :message message
             :value value
             :line-number line-number))
    

     

    Парсим CSV-файл

    The parser converts raw CSV text into a list of lists -- each item in these lists corresponds to a field in the CSV.

    Парсер преобразует CSV-файл в список списков, каждый элемент которых соответствует полю CSV-файла.

    (defun parse-csv-file (file)
      (with-open-file (f file :direction :input)
        (loop
           for line = (read-line f nil)
           while line
           collect (cl-ppcre:split "," line))))
    

     

    Валидатор (без перезапусков)

    Finally, we get down to writing the validator, validate-csv. If the validation is succesful (i.e. all the fields in the CSV are valid), the function returns normally. If any invalid field is present, an error will be signalled (using the validator functions defined above).

    Наконец мы дошли до написания валидатора, validate-csv. В случае успешной валидации (т.е. когда все записи верны) происходит обычный возврат из функции. Если же обнаружена неверная запись, валидирующие функции сигнализируют об ошибке.

    This version of the validator doesn't contain any restarts though.

    Эта версия валидатора пока что не содержит т.н. перезапусков.

    (defun validate-csv (file)
      (destructuring-bind (headers . rows)
          (parse-csv-file file)
        (loop
           for row in rows
           for line-number upfrom 2
           do
           (when (/= (length row) (length headers))
             (csv-error "Number of fields doesn't equal number of headers."
                        :line-number line-number))
           (handler-bind
               ;; Set the LINE-NUMBER slot of the signalled
               ;; csv-error. Note that since this clause returns normally,
               ;; the error doesn't stop here, it goes "up" the stack
               ((csv-error #'(lambda (c)
                               (setf (csv-error-line-number c) line-number))))
             (loop
                for header in headers
                for field in row
                do (validate-field header field))))))
    
    ;; Takes a header name and a string value as arguments; checks the
    ;; validity of the value by calling the appropriate validator function
    (defun validate-field (header value)
      (flet ((!header-matches (string)
               (string-equal header string)))
        (cond
          ((!header-matches "url") (validate-url value))
          ((!header-matches "rating") (validate-rating value))
          ((!header-matches "visitors") (validate-visitors value))
          ((!header-matches "date") (validate-date value))
          (t (csv-error "Invalid header." :value header)))))
    

     

    Добавляем перезапуски

    There are a few actions we can take once an "invalid" field has been detected (i.e. a csv-error is signalled), e.g. we can abort the validation, we can continue validation on the next row, or we continue validation with the remaining fields in the same row (to name just a few).

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

    Aborting the validation is as simple as invoking the ABORT restart in the debugger, or doing something like this:

    Прекращение валидации произойдет если выбрать перезапуск ABORT в отладчике, или если использовать код вроде такого:

    (handler-case (progn
                    (validate-csv "~/tmp/test.csv")
                    :success)
      (csv-error () :failure))
    

    To continue validation on the same row, or the next one, we add a couple of restarts using the with-simple-restart macro:

    Чтобы продолжить валидацию с той же строки, или со следующей, добавим пару перезапусков используя макрос with-simple-restart:

    (defun validate-csv (file)
      (destructuring-bind (headers . rows)
          (parse-csv-file file)
        (loop
           for row in rows
           for line-number upfrom 2
           do
           ;; If this restart is invoked, validation will continue on
           ;; the next row
           (with-simple-restart (continue-next-row
                                 "Continue validation on next row.")
             (when (/= (length row) (length headers))
               (csv-error "Number of fields doesn't equal number of headers."
                          :line-number line-number))
             (loop
                for header in headers
                for field in row
                do
                (handler-bind
                    ((csv-error #'(lambda (c)
                                    (setf (csv-error-line-number c)
                                          line-number))))
                  ;; If this restart is invoked, validation will continue
                  ;; on the next field in the row
                  (with-simple-restart (continue-next-field
                                        "Continue validation on next field.")
                    (validate-field header field))))))))
    

    Time for some fun now. Pass an invalid file to the validator, and what do we see in the debugger? Two new restarts: CONTINUE-NEXT-FIELD, and CONTINUE-NEXT-ROW. Select any one of them to see the validation move forward. The ABORT restart should be present all the time, so we can end the validation any time we want.

    А теперь прикол! Дадим валидатору заведомо неверный файл и что мы видим в отладчике? Два новых перезапуска, CONTINUE-NEXT-FIELD и CONTINUE-NEXT-ROW. Выбор любого из них покажет, что валидация продолжается. Перезапуск ABORT должен присутствовать всегда, чтобы мы могли прекратить валидацию в любой момент.

     

    Начинаем всё с начала

    We'll add one more restart now: this will allow us to revalidate the whole file if an error is signalled.

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

    ;; Note that what was known as VALIDATE-CSV earlier is now called
    ;; VALIDATE-CSV-AUX.
    (defun validate-csv (file)
      (restart-case (validate-csv-aux file)
        (retry-file ()
          :report (lambda (stream)
                    (format stream "Retry validating the file ~A." file))
          (validate-csv file))))
    
    (defun validate-csv-aux (file)
      (destructuring-bind (headers . rows)
          (parse-csv-file file)
        (loop
           for row in rows
           for line-number upfrom 2
           do
           (with-simple-restart (continue-next-row
                                 "Continue validation on next row.")
             (when (/= (length row) (length headers))
               (csv-error "Number of fields doesn't equal number of headers."
                          :line-number line-number))
             (loop
                for header in headers
                for field in row
                do
                (handler-bind
                    ((csv-error #'(lambda (c)
                                    (setf (csv-error-line-number c)
                                          line-number))))
                  (with-simple-restart (continue-next-field
                                        "Continue validation on next field.")
                    (validate-field header field))))))))
    

    Now what happens if we pass an invalid file to validate-csv? We get the RETRY-FILE restart in the debugger. This means that we can fix the problematic field, save the file, and start the validation all over again, without having exited the debugger!

    Что произойдет теперь, если указать функции validate-csv неверный файл? Мы получим в отладчике новый перезапуск RETRY-FILE. Это значит, что мы сможем исправить ошибочную запись, сохранить файл и повторить валидацию с начала, и всё это не выходя из отладчика!

     

    Обработка перезапусков

    Apart from the debugger, we can also handle restarts using handler-bind and find-restart.

    Можно обрабатывать перезапуски не только в отладчике, используя handler-bind и find-restart.

    For example, the following function will continue validating the file as long as CSV-ERRORs are signalled and one of CONTINUE-NEXT-FIELD or CONTINUE-NEXT-ROW restarts is available. It collects those errors in a list and returns the same.

    Например, следующая функция будет продолжать валидировать файл, если при возникновении ошибки CSV-ERROR доступны перезапуски CONTINUE-NEXT-FIELD или CONTINUE-NEXT-ROW. Объекты ошибок будут собраны в список, который функция возвратит на выходе.

    (defun list-csv-errors (file)
      (let ((result nil))
        (handler-bind
            ((csv-error #'(lambda (c)
                            (let ((restart
                                   (or (find-restart 'continue-next-field)
                                       (find-restart 'continue-next-row))))
                              (when restart
                                (push c result)
                                (invoke-restart restart))))))
          (validate-csv file))
        (nreverse result)))
    

    If we want a non-programmer to use the validator, we can provide a way to upload the CSV file (e.g. using Hunchentoot) and give a nicely formatted output of list-csv-errors in the browser.

    Если мы хотим, чтобы не-программист использовал наш валидатор, мы должны обеспечить возможность загрузки файла на сервер () и предоставить в браузере красиво оформленный список list-csv-errors.

     

    Заключение

    What I really like about the condition system is how it allows one to defer decisions to higher-level functions. The low-level functions provide different ways to move forward in case of exceptions (this is what validate-csv does), while the higher-level functions actually get to decide what path to take (like list-csv-errors).

    Что мне больше всего нравится в системе условий (condition system), так это то, что она позволяет переложить принятие решений на функции более высокого уровня. Функции низкого уровня предоставляют различные стратегии при возникновении исключительных ситуаций (как это делает validate-csv), тогда как функции высокого уровня (такие как list-csv-errors) могут выбрать подходящую для них стратегию.

    If we wanted list-csv-errors to list only one error per each row, that change would have been trivial, thanks to the restarts we have provided. This separation of logic, IMHO, makes it a very elegant tool in dealing with problems like these.

    Если нам понадобится, чтобы функция list-csv-errors помещала в список только одну ошибку на каждую строку, то изменения в коде будут тривиальны благодяря предоставляемым перезапускам. Такое разделение логики в программе, на мой взгляд, является очень элегантным инструментом для решения подобных задач.

     

    Заметки

    1. http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
    2. Еще один хороший источник, статья Kent Pitman, "Condition Handling in the Lisp Language Family" -- http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
    3. Я следую соглашению использовать восклицательный знак (!) в качестве префикса для имен локальных функций, объявленных с помощью FLET и LABELS. Это позволяет проще их идентифицировать в коде как локальные.
    cl-json и plists
    Сначала несколько слов о JSON и Common Lisp.

    Есть достаточно распространённое мнение, что CL хорошо подходит для генерации всяких XML, HTML и т.п., и для решения подобных задач предлагается множество решений на базе s-выражений. Из своего опыта могу заключить, что подобное мнения однозначно является заблуждением и для генерации XML/HTML лучше всего подходят шаблоны, но в этом случае CL не имеет специфичных преимуществ над другими языками (ну кроме тех, что CL просто лучший язык).

    А вот JSON действительно очень хорошо подходит для использования с Common Lisp, ибо может непосредственно отображаться на списки, которые являются основной структурой данных в CL. Поскольку JSON также непосредственно отображается и на хэш-таблицы, которые являются основным типов данных для JavaScript (можно утверждать, что хэш-таблицы в JavaScript имеют такое же ключевое значение, как списки в CL), то связка CL<=>JavaScript с взаимодействием на основе JSON вообще смотрится очень выигрышной: и там и там без каких-либо дополнительных усилий можно непосредственно работать с наиболее естественной формой представления данных.

    В какой-то момент для работы с JSON в Common Lisp я выбрал библиотеку cl-json, уже точно не помню, что мной двигало, но наверное то, что это простое наиболее известное решение. Сейчас я подумываю о том, что бы изменить это решение, ибо авторы cl-json кажется уж очень увлеклись своими концепциями. Но не суть. Так вот, в этой библиотеке для представления объектов/хэшов используются alists, которые я считаю не удобными для использования человеком и предпочитаю строить свои решения на базе plists. Соответственно, имеющаяся у меня инфраструктура гораздо более дружественна к plists и я захотел переоренитровать cl-json на работу с plists. Добился я этого следующим кодом, который поясняет моё отношение к данной библиотеке - он хотя и довольно краток, но его нельзя написать без изучения исходников библиотеки:
    (defun encode-json (obj)
    (flet ((encode-json-list (list stream)
    (if (keywordp (car list))
    (json:encode-json-plist list stream)
    (json::encode-json-list-guessing-encoder list stream))))
    (let ((json::*json-list-encoder-fn* #'encode-json-list))
    (json:encode-json-to-string obj))))

    (defun decode-json (str)
    (flet ((accumulator-add-pkey (key)
    (json::accumulator-add (funcall json:*identifier-name-to-key*
    (funcall json:*json-identifier-name-to-lisp* key))))
    (accumulator-add-pvalue (value)
    (json::accumulator-add value)))
    (let ((json:*object-key-handler* #'accumulator-add-pkey)
    (json:*object-value-handler* #'accumulator-add-pvalue))
    (json:decode-json-from-string str))))


    CL-USER> (encode-json '(:a 1 :b (:c 2 :d 3) :e 4))
    "{\"a\":1,\"b\":{\"c\":2,\"d\":3},\"e\":4}"
    CL-USER> (decode-json (encode-json '(:a 1 :b (:c 2 :d 3) :e 4)))
    (:A 1 :B (:C 2 :D 3) :E 4)
    Да, я использую darcs-версию библиотеки, которая содержит важное изменение, почему-то не оформленное в виде какого-либо релиза.
    Дискуссия
    В комментариях к недавнему посту thesz о том "Зачем нужны выкрутасы с типами" завязалось обсуждение и в результате автор предложил мне расширить формат дискуссии и ответить в своём блоге на следующие вопросы:
    - почему вы считаете развитием явление, которому более подходит название "расширение использования",
    - почему вы считаете, что дизайн не имеет критериев сравнения,
    - в моём посте приведён пример "дизайна системы" на основе типов, причём присутствует не менее двух итераций, попроще и посложнее; и до этого писал похожие посты; почему вы считаете, что никто и никогда не показывает, как пользоваться типами при дизайне систем,
    - почему вы считаете, что для программирования не нужен ум (и это особенно интересно),
    - почему вы считаете не зафиксированное в документе ТЗ отсутствующим ТЗ,
    - известна ли вам теория тестирования,
    - знаете ли вы об использовании типов для автоматической генерации тестов в библиотеке QuackCheck,
    - можно ли протестировать корректность параллельной программы,
    - пожалуй, всё.
    Отказаться от данного предложения я, конечно, не могу.


    Здесь речь идёт о повышении в течении последних 20 лет популярности языков с динамической типизацией, что вряд ли является следствием развития CS. Ответ заключается в том, что развитие "практического программирования" это прежде всего не появление новых фич в языках и теоретических подходов, а прежде всего накопление и критическое переосмысление опыта разработки реальных систем. Результатом данного процесса, в первую очередь, является появление методик разработки, непосредственно не привязанных к используемым инструментам. Такие практики, как "парное программирование", "code review" или "тесты вперёд" вряд ли даже с натяжкой могут быть отнесены к области CS, но именно они вносят наиболее серьёзные изменения в процесс разработки ПО. Поэтому, процесс популяризации динамических языков я называю именно развитие поскольку в результате этого произошло (и продолжает происходить) существенное изменение методологии, а инструмент нельзя рассматривать отдельно от методик его использования. Возможно, Python и не блещет глубиной идей, но на развитие методологии разработки он оказал гораздо большее влияние, чем все наследники ML вместе взятые.

    > почему вы считаете, что дизайн не имеет критериев сравнения

    В википедии есть несколько определений дизайна, большинство хорошо подходят в том числе и к такому понятию, как "дизайн кода" и во всех присутствует слово - творчество. Творчество не имеет критериев сравнения, хотя порой мы и может отмечать отсутствие таланта у автора. По моему убеждению именно дизайн кода составляет творческую составляющую разработки программного обеспечения. Его нельзя измерить, но время и реальная практика могут показать был ли он удачным в конкретном случае.

    > в моём посте приведён пример "дизайна системы" на основе типов, причём присутствует не менее двух итераций, попроще и посложнее; и до этого писал похожие посты; почему вы считаете, что никто и никогда не показывает, как пользоваться типами при дизайне систем,

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

    > почему вы считаете, что для программирования не нужен ум (и это особенно интересно),

    Вообще-то, ничего такого я не говорил. А говорил, что количество ошибок в коде не связано с умом, а скорей со свойствами характера. Собственно, такое наблюдение я сделал как в результате непосредственной работы с другими программистами, так и в результате наблюдения за некоторым open-source проектами.

    Впрочем, у меня есть кое-какое мнение и о затронутой теме. Как мерить ум я не знаю, мне известна только методика измерения IQ, которая вызывает много нареканий. Но, за неимением другой, можно попробовать осторожно опереться на неё. Так вот, большинство программистов, с которыми я общался, имели уровень интеллекта выше среднего и думаю, что вполне можно считать, что большинство программистов имеют уровень IQ в районе 120-140. Где-то мне попадалась информация, что большинство Нобелевских лауреатов имеют уровень IQ в районе 130, а 120 встречается чаще, чем 140. Поскольку вряд ли деятельность, за которую вручают Нобелевские премии, является менее сложной, чем программирование, то можно утверждать, что для достижения выдающихся результатов после превышения определённого уровня IQ наиболее значимым являются личностные качества. Т.е. уровень IQ в 120 позволяет эффективно работать даже над весьма сложными задачами, а определяющим фактором для успеха являются свойства характера. Я не вижу каких-либо оснований утверждать, что программист с уровнем IQ в 140 будет более эффективен, чем программист с уровнем IQ в 120.

    > почему вы считаете не зафиксированное в документе ТЗ отсутствующим ТЗ

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

    > известна ли вам теория тестирования

    В своё время прочитал довольно нудную книгу "Введение в тестирование программного обеспечения" (Луиза Тамре), но там излагается скорей практическое руководство, чем какая-либо теория.

    > знаете ли вы об использовании типов для автоматической генерации тестов в библиотеке QuackCheck

    Нет.

    > можно ли протестировать корректность параллельной программы

    Протестировать можно всё, что угодно, а вот гарантировать обычно ничего нельзя. Впрочем, для веб-приложений используются системы, которые генерируют нагрузку на веб-сервер аналогичную реальному трафику. Повышая интенсивность нагрузки можно в достаточно сжатые сроки провести весьма качественное тестирование, которое, хотя ничего и не гарантирует, но обычно позволяет выявить практически значимые проблемы. Наверняка, схожие подходы можно использовать для тестирования большинства параллельных программ. Кстати, для тестирования потоков в SBCL под Windows, реализацией которых сейчас занимается dmitry_vk, как раз в том числе использовалось тестирование веб-сервера и тривиального генератора нагрузки.

    Кажется всё. Хотя пост и не посвящен обсуждение Common Lisp, но тем не менее я использую тэг lisp для того, что бы данный пост попал в Russian Lambda Planet.
    Небольшой опрос
    Решил сделать небольшой опрос на тему книг про Clojure. Это не значит, что я собираюсь писать книгу, но я думаю в этом направлении...
    Update: Ссылка на опросник, если вам неудобно заполнять его на blogspot...



    P.S. просьба попиарить этот опросник, особенно среди ява-программистов...
    Made with Common Lisp 2
    Я уже показывал ранее скриншот и скринкаст моего основного рабочего проекта, но у меня уже почти готова новая версия, я решил показать новый скриншот. Собственно, вот он:


    Скринкаста пока не будет, ибо всё таки функционал ещё готов не полностью.

    В отличие от предыдущей версии, эта работает не только с Firefox, но также с Chrome и Opera. И вообще, ориентируюсь на HTML5.

    Ключевое значение в данном проекте приобрела cl-closure-template, ибо позволила сделать интерфейс более сложным и одновременно заметно сократить размер и сложность JavaScript кода (а также оказывает заметное влияние на дизайн серверного кода), так что теперь я могу смело утверждать, что Common Lisp используется не только на серверной стороне, но так же имеет ключевое значение и на стороне клиента.
    Очередные исправления в sbcl-win32-threads

    По наводке avodonosov обнаружил и исправил еще два бага, связанных с нитями, приводящих к утечке памяти и хэндлов.

    Новая версия бинарника лежит тут: http://sites.google.com/site/dmitryvksite/sbcl-distr/sbcl-1.0.36.16-threads-3.msi.

    Миграция постов с LJ на blogspot
    Сижу, конвертирую базу своих LJ'шных постов и картинок для заливки на блогспот. Картинки слил с помощью некоего fotoup.pl (версия с WWW/Mechanize), посты сдампил в блогспотовский формат с помощью http://livejournal2blogger.appspot.com/. lj2blogger, правда, надо локально разворачивать и запускать непосредственно скрипт, иначе не работает.

    Хочу пересортировать галерии из бэкапного формата LJ в нечто удобное для заливки на Picasa, плюс поправить url'ы в дампе постов. Выяснил, что LJ промотал 4 моих картинки. Т.е. превью и ссылки из галерей на них есть, но если тыкнуть мышой, то получаю ошибки типа "The requested URL /13_49/pic/0001q4q9 was not found on this server." Бэкапов они не делают, похоже. Либо их софт/админы не могут определить факт потери данных. Интересно, посты у них тоже в результате крэша могут потеряться?...

    Кстати, сортировку клепаю на коленке с помощью, конечно, лиспа и archimag'овской cl-libxml2. Когда он её рекламировал, то использовал в примерах XPath. Мне это показалось очень удобным, сделал mental note на будущее. С XML у меня опыта, практически, нет, а с XPath вообще никогда не работал. Вобщем-то, штука сама по себе чёткая, а в сочетании с лиспом вообще шикарно получается :) Весь код на экран уместился.
    iolib.process
    Давно хотел, но то времени не было, то мотивация подводила, и вот вчера звёзды совпали и я написал iolib.process - очень простую библиотеку для создания дочерних процессов и взаимодействия с ними через стандартные потоки ввода/вывода. Её можно рассматривать как не зависимую от реализации альтернативу sb-ext:run-programm - реализация полностью выполнена на основе возможностей библиотеки iolib. По дизайну она существенно отличается от sb-ext:run-programm, которая представляется мне излишне запутанной: sb-ext:run-programm это, конечно, крутая вещь, но мне не очень понятны потребности человека, которые её разрабатывал. iolib.process требует git-версию iolib и должна работать, в принципе, на любых UNIX-системах. Можно было бы сделать и windows-версию (я писал когда-то соответствующий код на C++), но iolib не содержит необходимый инфраструктуры (да и windows у меня уже давно нигде нет). Для запуска дочерних процессов используется sh (которая, кажется есть на всех юниксах), так что нет потребности указывать полный путь или там каталоги поиска и можно даже вызывать конвейеры, например:
    CL-USER> (iproc:with-child-process (conveyer "cat | grep good" :stdin t :stdout t)
    (let ((*standard-output* (iproc:process-input conveyer)))
    (write-line "Haskell is bad")
    (write-line "Python is bad")
    (write-line "Common Lisp is good")
    (write-line "IMHO"))
    (iproc:process-input-close conveyer)
    (read-line (iproc:process-output conveyer)))
    "Common Lisp is good"
    GPL 3 - кусается
    Эх, первый раз испытал отрицательные эмоции от того, что какой-либо проект использует GPL 3. Речь о библиотеке cl-data-format-validation. Я связался с автором на предмет возможного распространения этой библиотеки на условиях Lisp LGPL и получил следующий ответ:
    Given Common-Lisps dynamic nature and the shortage of libraries (compared to more recent toy languages) I consider it appropriate to just use GPL3 - not LGPL. In the unlikely event someone wants to use my library code in a proprietary derivative they can contact me for a different license however I am not going to support them for free.
    Вот ну очень жаль :( И дело не в том, что данная библиотека делает что-то фантастическое. На самом деле это довольно просто решение. Но, при этом, это очень точно решение, предоставляющее общий подход для упрощения сразу целого ряда типовых для web-разработки проблем. И это очень соответствует моему пониманию разработки, что даже сложные проблемы имеют достаточно простые решения если найти "критические точки", проблема лишь в том, что бы правильно определить эти самые "точки приложения силы" (а это всегда не просто, хоть потом решение и кажется очевидным). И с этой позиции cl-data-format-validation представляется мне очень точным и грамотным решением.

    Отказаться от данной идеи выше моих сил, так что, видимо, придётся писать свою библиотеку на данную тему, но под Lisp LGPL, а также с блэкджеком и развратными женщинами, ну т.е. с тестами и поддержкой локализации. Где вот только время на это найти...
    @2009-2010 lisper.ru