Записки программиста
Некоторое время назад я наконец-то дочитал книжку «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 написано много готового кода, см cliki.net, cl-user.net, а также GitHub.
Что удивительно, 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.
Если вы захотите собрать приведенный код, воспользуйтесь следующей командой:
sbcl —load sysadmin-day.lisp —eval «(sb-ext:save-lisp-and-die \» sysadmin-day \» :executable t :toplevel ‘main)»
У меня сложилось какое-то двоякое впечатление о Common Lisp. CL — он почти как Python, только быстрый и безо всяких там GIL, поддерживает макросы и имеет больший уклон в сторону ФП. Писать на нем можно, несмотря на скобочки, странные имена функций типа cdr и тп. Вот я только не уверен, нужно ли.
Вакансий, связанных с CL, похоже, нет вообще и в обозримом будущем много не предвидится. То есть, если и заниматься Common Lisp, то исключительно в качестве хобби. Но не лучше ли тратить свободное время, например, на Haskell? Ведь он ничем не хуже, но при этом, кажется, куда актуальнее. С другой стороны, смотрится CL интересно… В общем, я весь в раздумьях.
Напоследок, небольшая подборка ссылок по теме:
- http://lisp.ru/ — крутой сайт с форумом, статьями, литературой и тп;
- http://habrahabr.ru/hub/lisp/ — хаб на Хабре;
- http://ru-lisp.livejournal.com/ — ЖЖ-сообщество, посвященное Lisp’ам;
- Книга Пола Грэма «ANSI Common Lisp» на русском языке в формате PDF за 1 рубль: http://www.books.ru/books/ansi-common-lisp-3127808/ (там же есть и печатная версия, но мне кажется, что она не стоит своих денег);
- Очень душевная статья того же Пола Грэма «Lisp: побеждая посредственность»: http://www.nestor.minsk.by/sr/2003/07/30710.html;
- Бесплатный русский перевод книги «Practical Common Lisp»: https://github.com/pcl-ru/pcl-ru (оригинал: http://www.gigamonkeys.com/book/);
- Краткий курс по Common Lisp на русском: https://github.com/filonenko-mikhail/ub-lisp;
- Припоминаю, что я заинтересовался Common Lisp и отправился читать PCL после ознакомления с Nikodemus’ Common Lisp FAQ: http://habrahabr.ru/post/143618/;
А что вы можете сказать о Common Lisp?
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.
Common Lisp
Part of what makes Lisp distinctive is that it is designed to evolve. As new abstractions become popular (object-oriented programming, for example), it always turns out to be easy to implement them in Lisp. Like DNA, such a language does not go out of style.
(select (:title :author :year) (from :books) (where (:and (:>= :year 1995) (:< :year 2010))) (order-by (:desc :year))) ⇒ ((:title "Practical Common Lisp" :author "Peter Seibel" :year 2005) (:title "ANSI Common Lisp" :author "Paul Graham" :year 1995))
Mature & Stable
An extensive standard provides a rock-solid foundation that you can confidently build upon. You won't be reinventing the same old wheels ten years from now.
Functional
Functions are first class objects: you can pass them around, store them, call them dynamically. Build your application by composing small, functional building blocks.
(reduce #'- (reverse (list 1 2 3))) ⇒ 0 (mapcar #'string-downcase (list "Hello" "world!")) => ("hello" "world!")
Object-Oriented
Build reusable and extensible class hierarchies using the Common Lisp Object System. Design patterns disappear as you adapt the language to your problem domain.
(defclass book () ((title :reader book-title :initarg :title) (author :reader book-author :initarg :author)) (:documentation "Describes a book.")) (make-instance 'book :title "ANSI Common Lisp" :author "Paul Graham")
Fast
Great Tools
SLIME, an IDE that leverages the power of Common Lisp and the extensibility of Emacs, provides a development environment ahead of anything else. You can leave the write-compile-debug cycle behind. Everything is interactive: try your code on the REPL as you write it, and a powerful debugger lets you inspect trees of live values, or rewind the stack to undo an exception.
Grammarly is a grammar checking startup, but it’s far more than a simple spell checker. Its grammar engine, written in Common Lisp, finds instances of incorrect tenses and suggests more precise synonyms for common words.
At Grammarly, the foundation of our business, our core grammar engine, is written in Common Lisp. It currently processes more than a thousand sentences per second, is horizontally scalable, and has reliably served in production for almost 3 years.
Lisp was the natural language to start with. We needed to write lots of code very quickly; and we needed the higher-level power that only Lisp and Allegro CL provides. Lisp provided us with the ability to write the algorithms that we needed. We can search thousands of pricing and scheduling options in the time it takes the other airline engines to search several hundred. And, thanks to our lisp-based algorithms, we can adapt our questions to become more narrow or broad depending on the situation.