Pull to refresh

Nikodemus' Common Lisp FAQ

Reading time 11 min
Views 11K
Original author: Nikodemus Siivola
Nikodemus' Common Lisp FAQ


Последнее обновление: 2012-04-13

Это очень неофициальное ЧаВО по языку Common Lisp, отражающее субъективное мнение Nikodemus Siivola. Этот текст частично основан на других ЧаВО, встречавшихся на просторах интернета и comp.lang.lisp.
Если вы думаете, что я воспользовался вашим текстом, буду рад признаться в этом, поэтому пишите на nikodemus@random-state.net.
Текущая версия этого ЧаВО доступна по следующим адресам:
http://random-state.net/files/nikodemus-cl-faq.txt
http://random-state.net/files/nikodemus-cl-faq.html
ЧаВО также периодически публикуется в comp.lang.lisp.

Самое начало


Common Lisp? CL? Clisp? Lisp?

«Common Lisp» — название языка, стандартизированного ANSI.
«CL» — наиболее предпочтительное сокращение предыдущего названия.
«Clisp» не является правильным сокращением, поскольку это название одной из реализаций Common Lisp.
«Lisp» — это категория языков, к которым относится CL.
«LISP» уже не пишут лет 20, Common Lisp часто сокращают до Lisp, если
ясно из контекста, о чём именно идёт речь.

И что?

Про сам язык почитать можно тут:
http://random-state.net/features-of-common-lisp.html

Как научиться Common Lisp?

  1. Прочитать хорошую книгу о Common Lisp.
  2. Начать его использовать.

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

Какие подводные камни обходить?

Любая хорошая книга расскажет вам об особенностях и трюках языка, но есть пара психологических и социальных моментов, на которых многие спотыкаются:

  1. Изучение макросов Lisp сводит с ума.
    Макросы Lisp — классная штука, но сила, которую они дают, новичков часто сбивает с толку.
    Часто симптом проблемы — попытка что-то сделать без ясного понимания того, зачем это делается. Запомните, все что можно сделать с помощью макросов, можно сделать и без них. Конечно, может получиться не так практично, но память об этом опускает с небес на землю.
    Перед тем, как научиться бегать, научитесь ходить. Оно того стоит, тем более что потом можно и полететь.
  2. Lisp не идеален, и это не новость.
    Некоторые приходят к Lisp'у с завышенными ожиданиями и разочаровываются. Другие сравнивают Lisp с языком X и обнаруживают, что первый в чем-то проигрывает.
    И те, и другие могут рассуждать правильно и иметь обоснованные претензии, но проблемы начинаются тогда, когда эти люди начинаютозвучивать свои претензии где-нибудь, например на #lisp.
    Если рассуждения правильные, скорее всего вопрос уже обсуждался сотню раз и ни у кого уже не хватает ни сил, ни желания на повторное обсуждение.
    Если рассуждения неправильные или они носят скорее теоретический нежели практический характер, то несмотря на то, что год от года недовольные новички постоянно поднимают подобные вопросы, ни у кого уже не хватает ни сил, ни желания на повторное обсуждение.
    Это не значит, что говорить в интернете нехорошее про Lisp запрещено, но… Если вы — новичок — приходите и говорите, что все плохо, не ожидайте что к вам люди пойдут с распростертыми объятиями. Даже если вы сто раз правы.
    Жалобы от старичков, к тому же сделавших немало для сообщества, имеют гораздо больший вес.
  3. Опытные публичные лисперы видят много троллей.
    МНОГО ТРОЛЛЕЙ! Растерянный новичок иногда выглядит как тролль, во многом потому, что большинство троллей чаще всего маскируются под новичков Lisp.
    Поэтому люди могут думать что вы тролль и отвечать соответственно.
    Самый лучший способ избежать этого — быть вежливыми. Если кто-то вам говорит, что вы заблуждаетесь или ошибаетесь, на минутку предположите, что он или прав, или очень похоже, что вы заблуждаетесь или ошибаетесь.


С какой книги начать?

Начните с «Practical Common Lisp» (известной также как PCL) Питера Сайбела (Peter Seibel). Это хорошая отправная точка, если вы уже владеете каким-то языком программирования. Книга доступна в электронном и печатном виде:

http://www.gigamonkeys.com/book/

Другая хорошая книга — это «Common Lisp: A Gentle Introduction to Symbolic Computation» Девида Турецкого (David Touretzky). Новичокам в программировании или тем, кому PCL показалась слишком сложной, стоит почитать эту книгу. Если вы начнете с неё, потом всё же прочитайте PCL. Хотя если вы уже прочитали PCL и всё поняли, эту можно смело пропустить. Книга доступна в электронном и печатном виде:

http://www.cs.cmu.edu/~dst/LispBook/index.html

Есть еще куча хороших книг, но указанные две — лучшие для новичков. «Land of Lisp» неплоха, но по моему скромному опыту она может сформировать неправильное представление. Если начнете с нее, все равно прочитайте PCL.

Обязательно познакомьтесь с «Hyperspec» или CLHS, электронной версией стандарта языка. Это просто ценнейший справочник:

http://www.lispworks.com/documentation/HyperSpec/index.html

Не надо сразу бросаться читать справочник. Просто знайте где он находится, здесь содержатся официальные ответы на все возможные вопросы про CL. Это идеальное место для выяснения, что делает тот или иной оператор. Кроме того, в него стоит заглядывать, чтобы узнать, реализован ли нужный вам функционал в языке.

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

http://www.sbcl.org/manual

Какую мне взять реализацию?

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

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

Я очень неравнодушен к SBCL:

http://www.sbcl.org/

SBCL хорош тем, что он идет с открытым исходным кодом, запускается на множестве платформ (в том числе Windows), включает в себя компилятор, очень серьезно относится к совместимости со стандартом ANSI и вообще несет радость и мир во всем мире… и тут я должен упомянуть, что я один из разработчиков SBCL и моя компания Steel Bank Studio Ltd предоставляет коммерческую поддержку для него.

Если SBCL по каким-то причинам вам не подходит, могу предложить пройтись по следующим спискам:

Открытые:
  • Clozure CL
  • CMUCL
  • Clisp
  • ABCL
  • ECL


Коммерческие:
  • Lispworks
  • Allegro CL
  • Scieneer


Где библиотеки? Есть аналог CPAN или RubyGems?

На RubyGems очень похож «QuickLisp»:

http://www.quicklisp.org/

Он предоставляет массу библиотек и управляет зависимостями между ними. Это очень-очень нужный инструмент.

Наиболее близкими к CPAN являются «Cliki» и «common-lisp.net»:

http://www.cliki.net/
http://www.common-lisp.net/

… но лучше ничего и не надо, правда.

Каким IDE пользоваться?

Если вы работаете с SBCL, то пользуйтесь Emacs и Slime:

http://www.common-lisp.net/project/slime/

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

Можно настроить Slime с помощью Quicklisp, смотри ниже раздел «Как настроить окружение?». Начать изучение можно с M-x slime-cheat-sheet, впрочем, это лишь малая часть возможностей Slime.

При использовании другой реализации пользуйтесь тем IDE, что рекомендует разработчик (хотя Slime работает практически со всеми реализациями).

Для практической работы с Lisp редактор как минимум должен:

  • правильно расставлять отсупы в коде Lisp;
  • понимать парные скобки;
  • уметь выполнять команды Edit Definition, Compile Defun, Eval Expression, Describe Symbol, Disassemble Function, Trace Function, Inspect expression и другие команды для взаимодействия с вашим Lisp'ом;
  • взаимодействовать с отладчиком;
  • взаимодействовать с инспектором.


Slime умеет все перечисленное и ещё много чего.

Если вам нравится Vi(m), обратите внимание на Slimv, который связывает Vim с частью Slime, написанной на Common Lisp:

http://www.vim.org/scripts/script.php?script_id=2531
https://bitbucket.org/kovisoft/slimv/
http://kovisoft.bitbucket.org/tutorial.html

… но я не могу ручаться за это, поскольку не пользуюсь Vim/Slimv.

Как настроить окружение?


Хорошее руководство (на момент написания) по получению SBCL, Slime и настройке Quicklisp расположено здесь:

http://mohiji.nfshost.com/2011/01/modern-common-lisp-on-linux/
http://mohiji.nfshost.com/2011/01/modern-common-lisp-on-osx/

Указания по настройке Clisp на Windows. Впрочем, нельзя объять необъятное:

http://mohiji.nfshost.com/2011/01/modern-common-lisp-on-windows/

А GUI есть?

И да, и нет. Одного GUI, которым пользовались бы все, нет.

Коммерческие Lisp'ы в большинстве своём поставляются с библиотеками GUI, и, похоже, что сторонникам этих реализаций нравятся поставляемые библиотеки. Однако, код для таких библиотек не переносится между Lisp'ами. Если вы пользуетесь коммерческой реализацией и переносимость кода вам не интересна, то выбирайте инструменты, предлагаемые разработчиком. В зависимости от того, как сделана библиотека, код может переносится на разные операционные системы, возможно это именно то, что вам нужно.

В лагере отрытого кода тоже есть несколько решений.

CommonQt — это привязка Common Lisp к библиотеке smoke для Qt:

http://common-lisp.net/project/commonqt/

LTK построена поверх Tk:

http://www.peter-herth.de/ltk/

CL-GTK2 и CLG — привязки к GTK+, но я не могу ничего сказать про текущее состояние этих разработок. Стоит также посмотреть на GTK Server.

http://common-lisp.net/project/cl-gtk2/
http://sourceforge.net/projects/clg/ http://www.gtk-server.org/

CLIM (Common Lisp Interface Manager) — это почти стандартизированная спецификация API для GUI, довольно сильно отличающаяся от GUI, перечисленных выше. Не ожидайте, что все будет знакомо и понятно.

http://random-state.net/files/how-is-clim-different.html

Многие клянутся, что это лучшее, что есть для построения GUI, другие утверждают, что это совсем не так. Как бы то ни было, большинство коммерческих Lisp'ов реализуют CLIM, и еще есть переносимая открытая библиотека, называемая McCLIM, довольно удобная, хотя в последнее время не особо развиваемая.

http://common-lisp.net/project/mcclim/

CLX — это переносимый низкоуровневый Lisp-интерфейс к X11, предоставляющий уровень абстракций, сопоставимый с Xlib.

http://www.cliki.net/CLX
https://github.com/sharplispers/clx

Если не строго ограничиваться вопросами GUI, буду неправ, если не упомяну CL-OPENGL, переносимую привязку к API OpenGL, GLU и GLUT:

http://common-lisp.net/project/cl-opengl/

Какие есть форумы?

Не форум конечно, но есть Planet Lisp — агрегатор блогов по Common Lisp. Много интересной информации, без избытка.

http://planet.lisp.org/

LispForum — просто хороший форум:

http://www.lispforum.com/

но я не ручаюсь, поскольку бываю там нечасто.

Есть ещё группы comp.lang.lisp на Usenet/Google Groups, но они густо населены троллями. Пишущие там авторы довольно грамотны, при этом спекуляции профанов — рядовое явление. Читать эти группы может быть тяжело, но чтобы пользоваться Lisp'ом, читать их не обязательно.

http://groups.google.com/group/comp.lang.lisp

Специализированные списки рассылки имеют намного лучшее отношение сигнал/шум. Все реализации стараются завести собственные пользовательские и справочные списки рассылки, большинство библиотек также создают собственные списки рассылки. Для SBCL есть, например, это:

https://lists.sourceforge.net/lists/listinfo/sbcl-help

Среди open-source разработчиков и пользователей популярен канал #lisp на freenode.org. Имейте, впрочем, ввиду, что на #lisp довольно жестко придерживаются темы, и эта тема Common Lisp, а не «Lisp вообще». Для этого есть канал #lispcafe с гораздо более мягкими правилами.

Довольно активно сообщество разработчиков игр, но я не особо с ним знаком. Гугл вам в помощь.

Профессионалы Common Lisp общаются в списке «pro». Обсуждение других диалектов Lisp'а является офтопиком, вопросы новичков НЕ принимаются.

http://lists.common-lisp.net/mailman/listinfo/pro

Свойства языка


Как скомпилировать файл?

Короткий ответ: запускаете Lisp и печатаете:

(compile-file "/path/to/myfile.lisp")


Затем, скорее всего, вам потребуется загрузить (load ...) компилированный файл.

Развернутый ответ: большинство компилируемых языков неинтерактивны — вы компилирует файл из командной строки или IDE, затем запускаете компилированный файл. В Lisp'е все не так.

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

Обычно взаимодействие происходит с запущенным Lisp-процессом, который содержит рабочую сессию, в которую вы интерактивно добавляете код.

Например:

  1. открываем Emacs, с помощью M-x slime запускаем Slime и Lisp;
  2. с помощью, к примеру, ASDF, загружаем имеющийся код;
  3. открываем нужный файл, правим функцию и нажимаем C-c C-c, что приведет её перекомпиляции;
  4. переходим в Slime REPL и тестируем изменения;
  5. повторяем с шага 3.


Упомянутая выше аббревиатура ASDF расшифровывается как «Another System Definition Facility». Этой система позволяет указать способ сбора нескольких файлов в единую систему для их загрузки или компиляции одной командой. Чем-то походит на Make.

Как сделать исполняемый файл?

Ответ зависит от используемой вами реализации. Смотрите в документации. Если говорить про SBCL:

;; Загружаете приложение в SBCL, затем выполняете команду save-lisp-and-die.
;; Точка входа в приложение - MY-FUNCTION.
(save-lisp-and-die "my.exe" :executable t :toplevel 'my-function)


FUNCALL и APPLY — в чем разница, что использовать?

Короткий ответ: везде, где можно используйте FUNCALL, в остальных случаях используйте APPLY.

Развернутый ответ: при вызове FUNCALL должно быть известно количество аргументов. APPLYMULTIPLE-VALUE-CALL) не требует информации о количестве аргументов.

(defun map-list-with-1 (function list arg)
   (mapcar (lambda (elt)
             (funcall function elt arg))
           list))

(defun map-list-with-n (function list &rest args)
   (mapcar (lambda (elt)
             (apply function elt args))
           list))


Незачем писать MAP-LIST-WITH-1 с помощью APPLY, вызов FUNCALL почти наверняка будет более эффективным.

В противовес MAP-LIST-WITH-N не может быть написан с использованием FUNCALL, поскольку количество аргументов вызывающей стороне неизвестно. Следует использовать APPLY.

SET, SETQ и SETF — в чем разница, что использовать?

Короткий ответ: всегда используйте SETF.

Развернутый ответ: Давным давно, когда еще не было Common Lisp, не было лексических переменных, были только динамические. И не было тогда ни SETQ, ни SETF, только SET.

То, что сегодня пишется как

(setf (symbol-value '*foo*) 42)


записывалось так

(set (quote *foo*) 42)


что со временем сократилось до SETQ (SET Quoted)

(setq *foo* 42)


Потом появились лексические переменные и SETQ стали использовать и для их присваивания, так что SETQ перестал быть просто оберткой вокруг SET.

Позже кто-то изобрел SETF (SET Field) как обобщенный способ присвоения значений в структурах данных, зеркальное отображение L-значений в других языках:

x.car := 42;


записывается как

(setf (car x) 42)


Для симметрии и общности, SETF также включает в себя функциональность SETQ. Можно сказать, что SETQ был низкоуровневым примитивом, а SETF — высокоуровневой операцией.

Потом появились символьные макросы. Поскольку символьные макросы прозрачны, было сделано так, что SETQ ведет себя как SETF в случае, когда присваиваемая «переменная» на деле символьный макрос:

(defvar *hidden* (cons 42 42))
(define-symbol-macro foo (car *hidden*))

foo => 42

(setq foo 13)

foo => 13

*hidden* => (13 . 42)


И вот мы попадаем в наши дни: SET и SETQ по сути атавизм, оставшийся от старых диалектов и, возможно, будет выкинут из того, что будет следующим Common Lisp'ом.

Всегда пользуйтесь SETF.

'(1 2 3) или (list 1 2 3)?

Короткий ответ: пишите

(list 1 2 3)


пока не поймете разницу. Если вы пишете

'(1 2 3)


не модифицируйте это деструктивно (т.е. с помощью SORT или NREVERSE).

Развернутый ответ: Во-первых, одинарная кавычка — это макрос, преобразующий

'anything


в

(quote anything)


во время чтения, так что

'(1 2 3) === (quote (1 2 3))


Во-вторых, QUOTE — это специальный оператор, возвращающий свои аргументы невычисленными. Так

'(1 2 3)


возвращает буквальный (literal) список. Как и в большинстве языков модификация буквальных (literal) данных приводит к неопределенным последствиям. Например, компилятор может соединить константы, содержащие литералы:


   (let ((a '(1 2 3))
         (b '(1 2 3)))
    (eq a b))            ; => T или NIL


Следствием является тот факт, что изменяя A, также может измениться и B. Тогда для чего годится QUOTE? Если, например, у вас есть большие неизменяемые списки, которые компилятор может соединить, то пометка их как буквальные (literal) дает компилятору право так поступить.

Что за *УШКИ*?

Что бы вы ни использовали для объявления переменных, DEFVAR или DEFPARAMETER, всегда делайте *ТАКОЕ-ИМЯ*. И не делайте так для локальных переменных.

(defvar *очень-хорошо* ...)
(defvar это-очень-плохо ...)


Зачем? Если вы еще не знаете, что такое специальные переменные, продолжайте читать ту книжку, которую читаете и возвращайтесь как закончите, а пока используйте ушки.

Ушки защищают от двух простых ошибок, которые очень легко сделать.

Ошибка 1: случайное связывание специальной переменной.


(defparameter foo "foo!")

(defun say-it ()
   (write-line foo))

(defun say-more (foo)
   (say-it)
   (format t "now say ~A~%" foo))


Теперь

(say-more "bar!")


напечатает

say bar!
now say bar!


вместо ожидаемого

say foo!
now say bar!


… упс!

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

Обычно вы получите предупреждение времени компиляции и ошибку времени выполнения в случае

(defun foo (bar)
   bat)


но если перед этим написать

(defparameter bat "baseball")


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

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

Отсутствие ушек создает ощущение ошибки: когда я вижу

(defparameter нет-ушек ...)


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

Всегда ставьте *ушки*. Говорят, из всех правил бывают исключения, но в данном случае очень трудно отыскать настоящее исключение из этого правила.

Хорошего кода,
— Nikodemus
Tags:
Hubs:
+28
Comments 6
Comments Comments 6

Articles