воскресенье, 9 октября 2011 г.

Впечатления от Clojure

Пришлось мне столкнуться по-службе с Clojure. Мои впечатления от знакомства:
  1. Первое, что бросается в глаза — это дурацкие [] и {} вместо расово верных (). Не скажу, что они упрощают чтение кода, ИМХО разница невелика. А вот написание усложняют (по крайней мере мне). Набирать закрывающие сущее мучение; вылазит вот такое ))}))]) безобразие и сиди считай скобки. Так недалеко и до JavaScript с его вечными });});});
  2. Зато, эти же самые [] и {} позволяют сделать конфиг с симпатичным синтаксисом почти-JSON.
    {
      :listen {
        :host "localhost"
        :port 8080
      }
      :db {
        :host "localhost"
        :port 5432
        :database "database"
        :user "root"
        :password "secret"
      }
    }
  3. Жутко неудобно писать императивный код. Мутабельность здесь не любят. Код, конечно, от этого становится чище, но времени на написание уходит чуть больше.
  4. Нет многострочных комментариев. Пришлось написать reader macro, добавляющий комментарии в стиле Common Lisp.
    (defn dispatch-reader-macro [ch fun]
      (let [dm (.get (doto
                       (.getDeclaredField clojure.lang.LispReader
                                          "dispatchMacros")
                       (.setAccessible true))
                     nil)]
        (aset dm (int ch) fun)))

    (defn read-comment [rdr pipe]
      (loop [s nil]
        (let [c (.read rdr)]
          (if-not (and s (= c (int \#)))
                  (recur (= c pipe))))))

    (dispatch-reader-macro \| read-comment)
    Единственное неудобство: нужно делать require во всех файлах, где эти комментарии используются.
  5. Очень понравился Leiningen. Собрать с его помощью jar-файл и запустить его на сервере оказалось очень просто. Сделано "для людей".

пятница, 1 апреля 2011 г.

GtkFileChooser и Win32

Вчера всплыла интересная проблема. File-chooser в CL-GTK2 не правильно работает с не-ASCII именами файлов.

Оказалось, что win32-версия GTK+ определяет два набора функций:
  • первый содержит те-же функции, что и unix-версия, но работающие с локальной кодировкой (а не utf8);
  • второй набор работает с utf8, но все функции имеют суффикс _utf8.
Самое интересное то, что файл gtkfilechooser.h с помощью препроцессора переименовывает функции из второго набора и скрывает из первого. DLL же экспортирует оба набора и CL-GTK2 как ни в чём ни бывало использует первый, неправильный набор.

Оказалось, что не все функции легко исправить потому, что они генерировались отдельным скриптом. Трудность заключалась в том, что нужно было генерировать reader macros. Символы #+win32 упорно не желали печататься как следует. Получалась никуда не годная ерунда вроде такой: |#+WIN32|

После долгих мучений родился такой workaround:

(defclass print-readtime-condition ()
  ((condition :initarg :condition)))

(defmethod print-object ((o print-readtime-condition) stream)
  (format stream "#~A" (slot-value o 'condition)))

Теперь макрос-генератор может выводить условия так:

(make-instance 'print-readtime-condition :condition :+win32)


Результаты ночных бдений: https://github.com/andy128k/cl-gtk2


вторник, 2 марта 2010 г.

Ещё один велосипед

В программе, которую я пишу, понадобилась компрессия/декомпрессия из ZLib. Казалось бы, такая простая и распространённая штука должна быть уже реализована и отлажена. Но оказалось, что не всё так просто, есть несколько библиотек, но ни одна из них не покрывает всех моих скромных нужд.

CL-ZIP: привязка к zlib. Всё бы ничего, но сделана на UFFI;
Salza/Salza2: умеет только компрессировать;
zlib: умеет всё, реализована на CL, но, как оказалось, работает через раз.

В итоге я написал ещё одну :) cl-z. Это простой биндинг к ZLib через CFFI.

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

ASDF и Windows

Что к чему

В asdf есть переменная *central-registry*, содержащая список директорий в которых ищутся asdf-системы. То есть, чтобы система была доступна
для загрузки, нужно чтобы путь к её asd-файлу был в *central-registry*.

Но систем много, и каждую прописывать в *central-registry* утомительно. Поэтому принято делать несколько иначе: прописывается путь к какой-то одной директории (например ~/.sbcl/systems/), а в эту директорию помещаются символические ссылки на asd-файлы.

Windows

Очевидно, что в windows нет ссылок и, казалось бы, такой вариант не подходит. Но, к счастью, asdf неплохо настраивается и можно это
ограничение обойти. Переменная *system-definition-search-functions* содержит список функций поиска asdf-систем.

Вот такая функция и код добавления её в asdf. Эта функция обходит все директории из *central-registry* и просматривает в них все lnk-файлы,
извлекает путь ссылки и сравнивает с именем искомой системы.

(defun search-for-system-in-windows (system)
  (let ((system-name (asdf::coerce-name system)))
    (dolist (dir asdf:*central-registry*)
      (dolist (lnk (directory (merge-pathnames (eval dir) "*.lnk")))
        (let ((system-path (parse-windows-shortcut lnk)))
          (when (string-equal system-name (pathname-name system-path))
            (return-from search-for-system-in-windows system-path)))))))

(pushnew #'search-for-system-in-windows
  asdf:*system-definition-search-functions*)


Этот код был бы неполным без этого фрагмента http://paste.lisp.org/display/91121

Здесь вся машинерия разбора lnk-файлов.

Варианты

Очевидно, что можно сделать всё это ещё проще. Можно вместо lnk-файлов придумать, скажем, symlink-файлы -- простые текстовые файлы с путями внутри. Тогда чтение таких файлов будет тривиальным и кода будет поменьше.

Любители Windows могут сделать регистрацию и поиск asd-файлов даже в реестре. :)

Ну а так как функций поиска может быть несколько, то можно все эти способы сочетать.

Резюме


Остаётся добавить этот код в пользовательский файл инициализации, и можно пользоваться.
Для SBCL это будет $HOME/.sbclrc
для Clozure CL: $HOME/ccl-init.lisp