Первые впечатления от Common Lisp
7 января 2013
Некоторое время назад я наконец-то дочитал книжку «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, я попробовал решить с его помощью задачу о дне системного администратора. Решение этой задачи на Erlang вы найдете в этой заметке, а на Perl и Haskell — в этой.
; день системного администратора в этом году.
; (c) Alexander Alexeev 2013 | http://eax.me/
; является ли год весокосным
(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.
Приведенный код собирается командой:
Узнать больше о Common Lisp можно по следующим ссылкам:
- https://habr.com/ru/hubs/lisp/ — хаб на Хабре;
- https://ru-lisp.livejournal.com/ — ЖЖ-сообщество, посвященное Lisp’ам;
- Книга Пола Грэма «ANSI Common Lisp» на русском языке: https://www.books.ru/books/ansi-common-lisp-827258/;
- Очень душевная статья того же Пола Грэма «Lisp: побеждая посредственность»: https://nestor.minsk.by/sr/2003/07/30710.html;
- Бесплатный русский перевод книги «Practical Common Lisp»: https://github.com/pcl-ru/pcl-ru (оригинал: https://gigamonkeys.com/book/);
- Краткий курс по Common Lisp на русском: https://github.com/filonenko-mikhail/ub-lisp;
- Припоминаю, что я заинтересовался Common Lisp и отправился читать PCL после ознакомления с Nikodemus’ Common Lisp FAQ: https://habr.com/ru/articles/143618/;
У меня сложилось какое-то двоякое впечатление о Common Lisp. CL — он почти как Python, только быстрый и безо всяких там GIL, поддерживает макросы и имеет больший уклон в сторону ФП. Писать на нем можно, несмотря на скобочки, странные имена функций типа cdr и тп. Вот я только не уверен, нужно ли.
Вакансий, связанных с CL, похоже, нет вообще и в обозримом будущем много не предвидится. То есть, если и заниматься Common Lisp, то исключительно в качестве хобби. Но не лучше ли тратить свободное время, например, на Haskell? Ведь он ничем не хуже, но при этом, кажется, куда актуальнее.
Метки: Функциональное программирование, Языки программирования.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.