Первые впечатления от 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

Чтобы немного попрактиковаться в 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 можно по следующим ссылкам:

У меня сложилось какое-то двоякое впечатление о Common Lisp. CL — он почти как Python, только быстрый и безо всяких там GIL, поддерживает макросы и имеет больший уклон в сторону ФП. Писать на нем можно, несмотря на скобочки, странные имена функций типа cdr и тп. Вот я только не уверен, нужно ли.

Вакансий, связанных с CL, похоже, нет вообще и в обозримом будущем много не предвидится. То есть, если и заниматься Common Lisp, то исключительно в качестве хобби. Но не лучше ли тратить свободное время, например, на Haskell? Ведь он ничем не хуже, но при этом, кажется, куда актуальнее.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.