пятница, 12 марта 2010 г.

Блеск и нищета SLIME

Блеск
Реальная разработка на на любом из диалектов Lisp, а на Common Lisp так тем более, не мыслима без хорошо инструмента, предоставляющего удобный интерфейс к мощным возможностям среды. Нет, конечно можно пытаться писать на CL в блокноте nano и вызывать, скажем, SBCL из командной строки, но серьёзных вещей так не напишешь и очень скоро придёшь к мысли, что лучше использовать что-нибудь вроде Python. И сейчас SLIME является тем самым инструментом, который делает разработку на Common Lisp очень удобной. Без сомнения, на сегодняшний день SLIME является достижение Common Lisp-сообщества, без которого бы уровень популярности CL был бы значительно ниже. Собственно, на этом блеск заканчивается :(

Нищета
Начну с утверждения того, что SLIME не является современным программным обеспечением и не устремлён в будущее, а скорей основан на пережитках прошлого (когда ситуация с CL была куда хуже, чем сейчас). Это, фактически, цельный кусок кода, который не опирается ни на что, кроме конкретных реализаций, и не предоставляет миру ничего, кроме собственно самой IDE. Кому-то может показаться, что это и неплохо, но мне представляется, что совершенно не раскрыты потенциальные возможности, которые возникают при разработке инструмента подобного рода.

SLIME состоит из двух частей: ELisp-кода, обеспечивающее непосредственный функционал IDE на стороне Emacs и Lisp-side части (известной как SWANK), которая является сервером, работающим в рамках запущенного lisp-процесса, который предоставляет доступ к "внутренностям" lisp и взаимодействует с elisp-частью по сокетному соединению: такая архитектура обусловлена тем, что использование стандартных потоков ввода/вывода для взаимодействия между lisp-процессом и Emacs не может обеспечить необходимый уровень сервиса. Связующим звеном между двумя этими частями, естественным образом, является протокол взаимодействия. Наличие данного протокола дало возможность для разработки SWANK-сервера также и на Scheme, а теперь и на Clojure (см. swank-clojure). Подобная схема выглядит просто замечательно: с одной стороны возможны различные реализации swank для разных диалектов lisp, с другой стороны возможны различные клиентские части, не только для Emacs, но и например для Eclipse (см. cusp), да и заметно упрощается (особенно после появления cl-gtk2) разработка полноценной среды разработки на самом Common Lisp (думаю, для fun на это нашлось бы достаточно желающих). В общих чертах, картина выглядит замечательно, но... как известно кругом овраги, а по ним ходить...

Протокол
Самое больное место. Протокол не стабилен и может меняться от коммита к коммиту (релизов или версий фактически нет). Реализации для Common Lisp и Scheme входят в состав SLIME, и можно изменить их, изменить ELisp-часть, закоминить, и свежая версия из CVS (которую рекомендуют использовать разработчики) будет продолжать успешно работать. Но вот тот же swank-clojure внезапно отвалится. Это основная причина, почему для работы с swank-clojure рекомендуется использовать замороженную версию SLIME. И я думаю, что это одна из основных причин, почему cusp не получил активного развития. Сообщество Clojure, вообще может иметь очень большое значение. Насколько я понимаю, сейчас плагин для Eclips может быть написан непосредственно на самом Clojure, что резко повышают вероятность его появления (fun от разработки на Clojure будет несомненно больше, чем от традиционного написания кода на Java). В данный момент, есть возможность получить такой плагин, который будет подходить и для работы с Clojure, и для работы с Common Lisp, и для работы с Scheme. А это было бы очень и очень важно получить полноценную среду разработки на Common Lisp на базе Eclipse (как известно, далеко не все любят Emacs). Но вот как долго swank-clojure будет опираться на старую версию SLIME для меня вопрос так даже не стоит: наиболее вероятный вариант, что эти ребята просто форкнут его под свои нужды и пойдут своим путем. Т.е. путем стандартизации протокола SWANK, введения для него версий, можно надеяться объединить представителей различных диалектов lisp для работы над общим окружением. А текущая ситуация приведёт к увеличению разобщённости.

Модульность
Как наиболее активный пользователь RESTAS я понимаю, что было бы неплохо внедрить в SLIME несколько сервисов, для облегчения работ с ним. Например, я бы хотел иметь удобную, на уровне IDE, возможность просмотра активных virtual host, а для них карты маршрутов (с переходом к месту определения конкретного маршрута и т.п.). Хотел иметь специальную версию профайлера, ориентированную именно на разработку веб-приложений, и информированную о структуре сайта, ну и удобный доступ к нему через Emacs, а не REPL. Или что бы режим для работы с шаблонами cl-closure-template знал об способе их включения в сайт и мог бы упрощать их пере-компиляцию. В принципе, сейчас можно написать необходимый код, но он будет зависеть от возможностей SLIME, которые для этого как бы не предназначены и могу измениться уже в следующем коммите SLIME. Я думаю, что стандартизованный способ расширения возможностей SLIME для работы с конкретными системами был бы очень полезен во многих случаях, но его нет, SLIME это вещь в себе.

Common Lisp SWANK
Common Lisp реализация SWANK с одной стороны содержит в себе много уникального функционала для независимого от реализации доступа к внутренностям Common Lisp, а с другой стороны, значительная часть кода может быть сокращена за счёт использования таких библиотек, как bordeax-threads, usocket, closer-mop и т.п. Т.е. с одной стороны выполняется двойная работа, также выполненная в других библиотеках. А с другой стороны, имеющийся уникальный функционал фактически недоступен для использования в других системах. Отсутствие зависимостей, конечно, несколько упрощает распространение, но при этом и затрудняет развитие. На мой взгляд, упрощения распространения должно обеспечиваться другими средствами (например, штатными менеджерами дистрибутивов или системами подобными clbuild) и на первый план должна выходить простота разработки, которая бы позволила привлечь к работе большее количество разработчиков. Т.е. с одной стороны, Common Lisp SWANK мог бы опираться на существующие (но которых не было в момент зарождения SLIME) библиотеки (для работы с сокетами, потоками и т.п.), а функционал, для которого сейчас нет переносимых реализаций необходимо выносить в отдельные проекты (например, работу с профайлером или интроспекцию), которые могут быть полезными и иметь значение сами по себе, а Common Lisp SWANK выполнял бы роль локомотива в процессе развития этих решений.

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

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

Нерекурсивная реализация функции Аккермана

Тут возник вопрос как вычислить функцию Аккермана. Проблема в том, что непосредственная рекурсивная реализация данной функции очень активно потребляет стэк и вычислить её для каких-нибудь чуть более интересных числе (например m=3, n=15) не получается, так как CLisp или даже SBCL потребляют весь стэк и дело заканчивается плачевно. Рецепт лечения очевиден: необходимо переписать функцию без использования рекурсии. Что-то меня в этой задаче зацепило и я это родил (особо впечатлительным не смотреть):
 (defvar *akk-cache* nil)

(defun akk (x y)
(let ((*akk-cache* (or *akk-cache*
(make-hash-table :test 'equal)))
(stack `((,x ,y))))
(flet ((akk-cache (m n)
(gethash `(,m ,n) *akk-cache*))
(new-value (value)
(setf (gethash (car stack) *akk-cache*)
value)
(cond
((not stack) t)
((not (second stack))
(pop stack))
((second (second stack))
(pop stack))
(t (let ((m (first (car stack)))
(n (second (car stack)))
(i (first (second stack))))
(pop stack)
(pop stack)
(push (list i (gethash `(,m ,n) *akk-cache*))
stack))))))
(loop
while stack
do (let ((m (first (car stack)))
(n (second (car stack))))
(if (akk-cache m n)
(pop stack)
(cond ((= m 0)
(new-value (1+ n)))
((and (= n 0)
(akk-cache (1- m) 1))
(new-value (akk-cache (1- m) 1)))
((= n 0)
(push (list (1- m) 1)
stack))
((and (akk-cache m (1- n))
(akk-cache (1- m)
(akk-cache m (1- n))))
(new-value (akk-cache (1- m)
(akk-cache m (1- n)))))
((akk-cache m (1- n))
(push (list (1- m)
(akk-cache m (1- n)))
stack))
(t (push (list (1- m))
stack)
(push (list m (1- n))
stack))))))
(akk-cache x y))))
Выглядит страшновато, но работает :)

понедельник, 8 марта 2010 г.

restas-0.0.5

Обнаружил в RESTAS досадный баг, исправил, пришлось оформить как релиз версии 0.0.5

воскресенье, 7 марта 2010 г.

Hunchentoot: упрощение отладки удалённых серверов

В hunchentoot версии 1.1 для управления отладочным режимом используется глобальная переменная *catch-errors-p*: если она установлена в значение, отличное от NIL, то hunchentoot перехватывает все conditions, в обратном случае следует вызов invoke-debugger и разработчик получает возможность разобраться в причинах ошибки с помощью стандартного отладчика. При разработке на машине разработчика никаких проблем не возникает, наоборот, это очень удобно. Однако, при отладке на боевых удалённых серверах (порой это необходимо) эта простая схема начинает представлять угрозу. Во-первых, на серверах с реальной нагрузкой если ошибка возникает на регулярной основе может случиться так, что отладчик будет активизироваться чаще, чем вы способны на это реагировать. Во-вторых, можно забыть сбросить значение *catch-errors-p* при разрыве соединения с удалённым swank-сервером, что приведёт к тому, что потоки начнут подвисать на вызове invoke-debugger и в конце концов система просто откажется работать. Таким образом, для удобной и безопасной отладки удалённых серверов необходимо ограничивать число возможных активных вызовов invoke-debugger, а также автоматически отменять отладочный режим при отключении от удалённого процесса. Сегодня в репозиторий hunchentoot был принят мой патч, решающий описанные проблемы следующим образом:
  • Постоянно поддерживается список отлаживаемых в данный момент потоков
  • Добавлен параметр *max-debugging-threads*: максимально возможное количество одновременно отлаживаемых потоков, значение по умолчанию - 5.
  • Функция debug-mode-on - активизирует отладочный режим.
  • Функция debug-mode-off - отменяет отладочный режим, имеет необязательный параметр kill-debugging-threads (по-умолчанию T) , который определяет надо ли уничтожать отлаживаемые в данный момент потоки.
  • В случая наличия в системе swank-сервера, в переменную swank::*connection-closed-hook* добавляется вызов debug-mode-off , который обеспечивает отмену отладочного режима и уничтожение отлаживаемых потоков после разрыва соединения.
  • Описанная схема применяется только к потокам, которые созданы hunchentoot для обработки запросов.
В поддерживаемой мной git версии репозитория hunchentoot данные изменения будут через пару дней (когда попаду на работу).

среда, 3 марта 2010 г.

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

Ранее я уже писал о символьных вычислениях, но в тот раз ограничился только общими впечатлениями и обещал написать отдельную большую статью. К таковой я, к сожалению, ещё не готов, но зато могу рассказать о том, как я использую символьные вычисления для реализации cl-closure-template, тем более, что последний троллинг на ЛОР-е буквально вынуждает меня к этому.

cl-closure-template - это система шаблонов для веб и казалось бы точно никакого отношения к символьным вычислениям иметь не может. И это, вероятно, действительно так если речь идёт о php. Но использование Common Lisp для решения этой задачи даёт богатую почву для использования символьных вычислений. Мало того, они чрезвычайно эффективны для решения данной задачи. Итак, cl-closure-template имеет следующие компоненты:

1. src/expression.lisp - парсер выражений, используемых в языке шаблонов. Это расширенная и переработанная версия файла infix.lisp из AIMA. Пусть дано выражение:
$a + $b[1] 
Первым делом оно считывается в символьную форму:
((:VARIABLE :A) + (:VARIABLE :B) [ 1 ])
Теперь полученную инфиксную форму необходимо привести к принятой в лисп префиксной нотации:
(+ (:VARIABLE :A) (ELT (:VARIABLE :B) 1))
Сам алгоритм преобразования представляется мне довольно сложным, но он был сложен ещё в AIMA и я поначалу не верил, что смогу его модифицировать, а cl-closure-template должна обрабатывать куда более сложные выражение, чем оригинальная версия (если кто забыл, то в AIMA этот код используется для разбора выражений логики первого порядка).

Т.е. здесь очевидно имеют две фазы вычислений, которые можно с полным правом отнести к символьным: преобразование строки в символьную форму и последующее приведение её к префиксной нотации.


2. src/template.lisp - собственно парсер шаблонов. Для описания грамматики языка шаблонов используются макросы define-mode, вот как выглядит описание тэга "for":
(define-mode for-tag (70 :all)
(:allowed :all)
(:entry "{for\\s+[^}]*}(?=.*{/for})")
(:entry-attribute-parser parse-for-attributes)
(:exit "{/for}"))
Информация, указанная в данной форме сохраняется в свойствах символа for-tag, который в последующем используется для сборки парсера, а сам символ for-tag будет использоваться в s-выражении, являющимся результатом разбора шаблона. Например, разбор следующего шаблона:
{template test}
{for $x in range(10)} ! {/for}
{/template}
вернёт такую символьную форму:
(closure-template.parser:template ("test")
(closure-template.parser:for-tag ((:variable :x)
(:range 10))
" ! "))
Таким образом, в данном компоненте можно отметить, что для описания грамматики шаблонов используется символьная форма, а результатом разбора шаблона является символьное выражение - весьма характерный пример использования символьных вычислений. Кроме того, в коде используется предоставляемая cl-ppcre возможность задания регулярных выражений в символьной форме, которая весьма существенно упрощает реализацию.

3. src/common-lisp-backend.lisp - предоставляет возможность использовать шаблоны в программах на Common Lisp. Шаблоны компилируются в машинный код (на поддерживающих это реализациях), для этого символьное представление шаблона преобразуется в код на Common Lisp, который, естественно, тоже имеет символьную форму. Опять получилось две фазы символьных вычислений: преобразование одной символьной формы в другую и выполнение её с помощью eval.

4. src/javascript-backend.lisp - аналогичен предыдущему компоненту, но только предназначен для генерации кода на JavaScript, для чего шаблон в символьной форме сначала преобразуется в символьный формат parenscript и затем с помощью parenscript компилируется в JavaScript. Опять совершенно явный пример символьных вычислений.

5. t/cl-backend-test.lisp - тесты для CL-бэкэнда, примечательным является то, что используемая библиотека lift сохраняет полную информацию о тестах в символьной форме в свойствах символов. Обычно это не имеет никакого значения, но оказалось очень важным для данного проекта.

6. t/js-backend-test.lisp - обеспечивает возможность тестирования JS-бэкэнда. Тесты для CL и JS-бэкэндов должны быть, вообще говоря, совершенно одинаковыми и я очень не хотел писать их руками, что бы потом ещё и мучатся с сопровождением. На помощь пришло описанное выше свойство lift. Код в данном файле извлекает полную информацию о тестах для CL-бэкэнда, в том числе код самих тестов, преобразует эти тесты в формат parenscript и компилирует с его помощью их в тесты для jsunittest.js. Кроме того, запускается веб-сервер и теперь можно смотреть результаты выполнения тестов в браузере. Я был чрезвычайно впечатлен этим ярким примером использования символьных вычислений.

Вывод: реализация cl-closure-template является примеров использования символьных вычислений, собственно, кроме символьных вычислений там больше и нет ничего.

У меня имеется ещё богатый материал по использованию символьных вычислений в cl-routes и RESTAS, да и вообще, чем больше я пишу на Common Lisp, тем больше использую возможности символьных вычислений, но об этом пожалуй в следующий раз.

понедельник, 1 марта 2010 г.

Новые версии моих пакетов

Вобщем, решил таки зарелизить все последние изменения в моих пакетах, итого:
  • cl-routes-0.2.1 (новая возможность описанная здесь)
  • cl-closure-template-0.1.4 (скомпилированные шаблоны должны стать быстрее)
  • restas-0.0.4 (подробности в предыдущем сообщении)
Кроме того, привёл в соответствие с новыми версиями свой форк gentoo-lisp-overlay, а также и форк clbuild.

P.S. Vladimir Sedach сообщил о релизе parenscript-2.1. В gentoo-lisp-overlay этого ещё нет, поэтому сделал сам для своего форка.

Модули в RESTAS

В последние дни провёл большую чистку RESTAS (а также и rulisp), по удалял устаревший код и переработал архитектуру модулей. На последнем моменте хотел бы остановиться подробней.

Изначально, в RESTAS было понятие site и понятие plugin, которые определялись через defsite и define-plugin соответственно. site можно было запустить как web-сайт, а plugin являлся элементом повторного использования, который (например, wiki или форум) можно было использовать повторно на различных сайтах. В результате проведённой переработки я избавился от обоих этих понятий, а вместо них ввёл единственное - module. Предыдущая схема была двухуровневой, текущая - иерархической.

Modules

С точки зрения Common Lisp module это просто package. С точки зрения RESTAS module это набор маршрутов (routes, подробнее о них можно почитать здесь), определяющих структуру web-приложения. module создаётся с помощью макроса restas:define-module, что приводит к созданию пакета с соответствующим именем, плюс проводится некоторая дополнительная инициализация этого пакета. Пример:
(restas:define-module #:hello-world
(:use :cl))
Теперь можно создать в этом модуле несколько маршрутов:
(in-package #:hello-world)

(define-route main ("hello")
"<h1>Hello world!</h1>")
Обращаю внимание на принципиальную важность размещения макроса define-route в пакете (после in-package), связанном с определённым модулем.

И наконец, полученный модуль можно запустить как web-сайт:
(restas:start '#:hello-world :port 8080)
В функцию restas:start (в предыдущих версиях данная функция называлась start-site) можно также передать ключевой параметр :hostname, что позволяет обслуживать несколько виртуальных хостов в рамках одного процесса.

Таким образом, module, разместив в нём несколько маршрутов, можно использовать для запуска web-приложения. Но у модулей есть ещё и другой вариант использования.

Submodules

Меня всегда волновала проблема повторного использования кода и занявшись web я заинтересовался как я могу повторно использовать код для, скажем, форума или вики, на нескольких сайтах, или даже несколько раз в рамках одного сайта? Отличие подобного web-приложения от простой библиотеки, содержащей функции, макросы, классы и т.п в том, что web-компонент также должен содержать информацию об обслуживаемых им url и эту информацию надо использовать в механизме диспетчеризации запросов, правильно определяя код, ответственный за обработку поступившего запроса. В терминах routes (маршрутов) любой повторно используемый web-компонент является просто списком обрабатываемых им маршрутов, а это как раз и есть то, чем являются modules с точки зрения RESTAS. Для повторного использования модуля в рамках другого модуля используется макрос define-submodule. Пример (зависит от кода выше):
(restas:define-module #:test
(:use :cl))

(in-package #:test)

(restas:define-submodule test-hello-world (#:hello-world))

(restas:start '#:test :port 8080)
В данном примере определяется новый модуль test, к нему присоединяется определённый выше модуль hello-world (а получившийся submodule ассоциируется с символом test-hello-world) и модуль test запускается на порту 8080. Хотя в самом модуле test не определён ни один маршрут, но в итоге он способен обрабатывать запросы, поступающие на /hello благодаря включению в себя модуля hello-world.

В таком виде данный функционал не выглядит очень полезным и вот почему. Для успешного повторного использования любого компонента надо уметь его конфигурировать, настраивать его параметры - без этой возможности повторное использование сведётся к технике copy/paste с последующим редактированием кода, что выглядит удручающее само по себе, а в контексте CL ещё имеет и множество технических ограничений (что связано с тем, что понятие package никак не связано с физическим размещением кода на файловой системе). В ООП традиционным способом решения проблем конфигурации является использование классов, но, хотя Common Lisp и имеет сверхмощную поддержку ООП (CLOS + MOP), я решил всё таки отказаться от подобного подхода: возникающие проблемы дизайна, проектирования, бесконечные интерфейсы и наследование всегда существенным образом повышают уровень сложности системы, что кажется мне совершенно излишним для такой простой области, как разработка web-приложений. Для решения этой проблемы в Common Lisp есть ещё один потрясающий механизм: динамические переменные. Сразу реальный пример кода, используемый на lisper.ru для публикации статических файлов:
(restas:define-submodule rulisp-static (#:restas.directory-publisher)
(restas.directory-publisher:*directory* (merge-pathnames "static/" *resources-dir*))
(restas.directory-publisher:*autoindex* nil))
В данном примере используется модуль restas-directory-publisher, о котором я уже писал ранее.

В модуле restas.directory-publisher определены (с помощью defparameter или defvar) несколько глобальных динамических переменных, которые можно использоваться для настройки его работы. В макросе restas:define-submodule некоторые из этих переменных связываются c новыми значения, но эти связывания не применяются непосредственно, а сохраняются в виде контекста для использования в будущем. При обработке запроса диспетчер находит маршрут, определяет связанный с ним submodule, настраивает окружение на основе сохранённого контекста и производит дальнейшую обработку запроса в рамках этого окружения (для этого используется вызов progv). Данный механизм напоминает Buffer-Local Variables в Emacs, я описывал вариант его реализации здесь.

При определении нового модуля с помощью restas:define-module в него (в пакет) добавляется переменная *baseurl*, которая должна быть списком строк и определяет базовый url, по которому будет активизирован данный модуль, по-умолчанию она установленная в nil. Данную переменную можно использовать в restas:define-submodule для задания url, по которому будет включён submodule. Вот более сложный пример использования модуля restas-directory-publisher на сайте lisper.ru (посмотреть этот код в работе можно по адресу http://lisper.ru/files/):
(restas:define-submodule rulisp-files (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("files"))
(restas.directory-publisher:*directory* (merge-pathnames "files/" *vardir*))
(restas.directory-publisher:*autoindex-template*
(lambda (data)
(rulisp-finalize-page :title (getf data :title)
:css '("style.css" "autoindex.css")
:content (restas.directory-publisher.view:autoindex-content data)))))
Кстати, вкупе с предыдущим, данный пример демонстрирует двукратное использование одного модуля с различными режимами работы в рамках одного и того же сайта, без каких-либо конфликтов между собой.

Внутренняя инициализация

Макрос restas:define-submodule позволяет контролировать настройку модуля "снаружи", но порой надо иметь возможность влиять на создаваемый контекст изнутри модуля. Например, модуль restas-planet, который используется для организации Russian Lisp Planet нуждается в механизме для сохранения объекта-робота (он по заданному расписанию считывает ленты и объединяет их в одну), который должен быть вычислен на основе переменных, которые могут быть помещены в контекст submodule. Для такого случая предусмотрен макрос restas:define-initialization, вот реальный код из planet.lisp:
(restas:define-initialization (context)
(restas:with-context context
(when *feeds*
(restas:context-add-variable context
'*spider*
(make-instance 'spider
:feeds *feeds*
:schedule *schedule*
:cache-dir (if *cache-dir*
(ensure-directories-exist (merge-pathnames "spider/"
*cache-dir*))))))))
Здесь производится вычисления объекта spider, который ассоциируется с переменной *spider* и помещается в контекст создаваемого submodule. Данный код будет вычислен при вычислении формы restas:define-submodule. Поскольку создание объекта spider приводит к запуску планировщика (в частности, создаётся таймер), то также надо уметь останавливать их при повторном вычислении формы restas:define-submodule, для этого предусмотрен макрос restas:define-finalization:
(restas:define-finalization (context)
(let ((spider (restas:context-symbol-value context '*spider*)))
(when spider
(spider-stop-scheduler spider))))

Дуализм

Описанная схема подразумевает дуализм: модуль как standalone-приложение, и он же как компонент повторного использования, что позволяет разрабатывать модуль без какого-либо учёта возможности повторного использования, а потом минимальной ценой (просто приводя его к "правильному" дизайну) превращать в многократно используемый компонент. Подобный подход, как мне кажется, позволяет в значительной степени избежать проблем, свойственных традиционному ООП-дизайну.

В качестве демонстрации, вот код для запуска restas-directory-publisher, который я использовал выше как повторно используемый компонент, в виде standalone-приложения:
(restas:start '#:restas.directory-publisher 
:port 8080
:context (restas:make-context (restas.directory-publisher:*baseurl* '("tmp"))
(restas.directory-publisher:*directory* #P"/tmp/")
(restas.directory-publisher:*autoindex* t)))
Теперь открыв в браузере страницу http://localhost:8080/tmp/ можно будет наблюдать содержимое директории #P"/tmp/".


P.S. Описанная архитектура доступна в git версии RESTAS и если вы захотите её опробовать, но ранее уже запускали более старые версии RESTAS, то настоятельно рекомендую удалить .fasl файлы, имеющие к нему какое-либо отношение.