Некоторое время назад я наконец-то дочитал книжку «ANSI Common Lisp». В действительности, это уже мой второй подход к CL. До этого я пытался читать «Practical Common Lisp», но как-то не пошло. Теперь же я осилил книгу полностью (ну почти, там приложений на 100 страниц…) и даже написал какую-никакую программку.

Краткие сведения о Common Lisp:

  • Lisp был создан Джоном Маккарти в 1958 году. Common Lisp появился в 1984 с целью объединения множества появившихся к тому моменту диалектов Lisp’а. Он был стандартизован ANSI в 1994. CL считается промышленным стандартом языка Lisp.
  • Еще два активно используемых нынче диалекта Lisp’а (если не считать диалекты для Emacs, AutoCAD и тп) — это Scheme и Clojure .
  • Common Lisp часто называют языком функционального программирования , но строго говоря он таковым не является. В нем есть побочные эффекты, изменяемые данные, традиционные циклы, ссылки, глобальные переменные и даже полноценное ООП. Однако существенная часть кода обычно все же пишется в функциональном стиле.
  • В CL используется строгая динамическая типизация , а также есть опциональная декларация типов. Есть автоматическое управление памятью . Все переменные являются ссылками на данные. Например, в коде ( progn ( setf x ( list 1 2 3 ) ) ( setf y x ) ( setf ( car x ) 4 ) y ) переменные x и y ссылаются на один список. При изменении первого элемента списка x также изменяется и список y.
  • Синтаксис CL очень прост, но одновременно и несколько непривычен большинству программистов. Выражения представляются в префиксной записи: ( форма аргумент 1 аргумент 2 ... ) , где форма является специальным оператором, макросом или функцией. Аргументы могут представлять собой атомы (числа, символы) или другие выражения. Описание всего синтаксиса языка в BNF занимает строк десять .
  • Таким образом, код представляет собой обычную структуру данных CL и может быть обработан, как любые другие данные, программой на самом CL. Этим занимаются макросы. За счет макросов Lisp постоянно эволюционирует, благодаря чему и остается на плаву вот уже более 50-и лет. Похвастаться тем же может разве что Fortran.
  • CL является интерпретируемым и компилируемым языком. Скомпилированная программа не зависит от наличия интерпретатора, определенной виртуальной машины и тп.
  • В CL можно удобно работать с комплексными и рациональными числами без использования каких-либо библиотек: ( = ( + 1 / 2 2 / 3 ) 7 / 6 ) .
  • Существует транслятор Common Lisp в Си , а также компилятор CL для Android и iOS .

Что удивительно, Common Lisp при его динамической типизации является числодробилкой не хуже, чем Java или Haskell:

Бенчмарк Common Lisp

Чтобы немного попрактиковаться в Common Lisp, я попробовал решить с его помощью задачу о дне системного администратора. Решение этой задачи на Erlang вы найдете в этой заметке , а на Perl и Haskell — в этой .

; Определяем, на какое число приходится
; день системного администратора в этом году.
; (c) Alexander Alexeev 2013 | http://remontka.com/

; является ли год весокосным
( defun leap-year? ( y )
( cond ( ( = 0 ( mod y 400 ) ) t )
( ( = 0 ( mod y 100 ) ) nil )
( ( = 0 ( mod y 4 ) ) t )
( t nil ) ) )

; сколько дней в заданном году
( defun days-in-year ( y )
( if ( leap-year? y ) 366 365 ) )

; количество дней в месяце в зависимости от типа года
( defun days-in-month ( month leap )
( cond ( ( member month ‘ ( april june september november ) ) 30 )
( ( eql month ‘february ) ( if leap 29 28 ) )
( t 31 ) ) )

; готового аналога range в CL не предусмотрено 🙁
( defun range ( max & key ( min 1 ) ( step 1 ) )
( loop for n from min to max by step
collect n ) )

; список пар месяц:номер
( defun months-numbers ( )
( mapcar #’ cons
( january february march april may june
july august september october november december )
( range 12 ) ) )

; получение номера месяца
( defun month- > number ( m )
( cdar
( remove- if
( lambda ( x ) ( not ( eql ( car x ) m ) ) )
( months-numbers ) ) ) )

; получение месяца по его номеру
( defun number- > month ( n )
( caar
( remove- if
( lambda ( x ) ( / = ( cdr x ) n ) )
( months-numbers ) ) ) )

; список месяцев, которые идут перед данным
( defun months-before ( m )
( if ( eql m ‘january ) ( )
( let ( ( prev ( number- > month ( ( month- > number m ) 1 ) ) ) )
( cons prev ( months-before prev ) ) ) ) )

; число дней с 1-го января 1-го года (не включая)
( defun days-from-epoch ( y m d )
( let ( ( is-leap ( leap-year? y ) ) )
( +
( reduce #’+ ( map ‘ list #’days-in-year ( range ( — y 1 ) ) ) )
( reduce #’+ ( map ‘ list
( lambda ( x ) ( days-in-month x is-leap ) )
( months-before m ) ) )
( — d 1 ) ) ) )

; определяем день недели, 0 — понедельник, 6 — воскресенье
( defun day-of-week ( y m d )
( mod ( days-from-epoch y m d ) 7 ) )

; на какое число приходится день сисадмина в заданном году
( defun sysadmin-day-in-year ( y )
( car ( last
( remove- if
( lambda ( x ) ( / = 4 ( apply #’day-of-week x ) ) )
( map ‘ list
( lambda ( x ) ( list y ‘july x ) )
( range 31 ) ) ) ) ) )

( defun main ( )
( format t
«Поздравить знакомых админов: ~S~%»
( sysadmin-day-in-year 2013 ) ) )

Разрабатывалось все это в связке из оконного менеджера i3 , текстового редактора vim , компилятора sbcl и утилиты rlwrap. В целом получилось довольно удобно, хотя я слышал, что гуру Lisp’ов предпочитают писать код в Emacs.

Приведенный код собирается командой:

sbcl —load sysadmin-day.lisp —eval «(sb-ext:save-lisp-and-die » sysadmin-day «» :executable t :toplevel ‘main)»»

Узнать больше о Common Lisp можно по следующим ссылкам:

У меня сложилось какое-то двоякое впечатление о Common Lisp. CL — он почти как Python только быстрый и безо всяких там GIL

EnglishRussianUkrainian