steal-your-face-plt

«Lisp стоит изучить хотя бы ради того глубокого просветления, которое наступает, когда наконец его понимаешь». — Эрик Реймонд

Racket — язык из одной из старейших и самых необычных семей программирования, где код является данными, скобки — чистой структурой, а программы способны писать программы. Материал знакомит с историей Lisp и постепенно подводит к написанию собственного синтаксиса на Racket.

Немного истории

Lisp родился в 1958 году — его изобрёл Джон Маккарти в MIT. Для сравнения: это второй по возрасту высокоуровневый язык, всё ещё используемый сегодня (только Fortran, появившийся в 1957-м, старше на год). Python появился в 1991-м, JavaScript — в 1995-м. Lisp опережает их более чем на 30 лет, и именно в нём родились идеи, которые сейчас считаются «современными»:

  • Сборка мусора — изобретена именно для Lisp.
  • Функции первого класса — передача функций как аргументов, сегодня стандарт повсюду.
  • REPL — интерактивный цикл «читать-выполнить-напечатать», который сегодня есть в Python, Node и Julia, зародился в Lisp.
  • Условия как выраженияif, возвращающий значение.
  • Гомоиконичность — код является структурой данных самого языка. Это ключевая идея, к которой материал вернётся в конце.

Десятилетиями Lisp оставался языком искусственного интеллекта. В 70-х и 80-х существовали физические компьютеры, спроектированные для запуска Lisp напрямую — Lisp-машины от Symbolics и LMI. Затем наступила «зима ИИ», финансирование иссякло, и Lisp превратился из звезды в культовый язык.

Но интересные идеи не умирают — они мутируют, и в этом часть красоты лиспов.

От Lisp к Scheme и к Racket

В 1975 году Джеральд Сассман и Гай Стил создали Scheme — минималистичный, элегантный, почти математический диалект Lisp. Scheme стал любимым языком академической среды для обучения программированию (легендарная книга SICP — Structure and Interpretation of Computer Programs — написана именно на Scheme).

В 1995 году группа Матиаса Феллейзена создала PLT Scheme — диалект, разработанный для образования и исследований языков программирования. В 2010 году его переименовали в Racket, и сегодня это куда больше, чем Scheme: это язык для создания языков. Неофициальный девиз — language-oriented programming: если задаче нужен собственный язык, Racket позволяет собрать его за один вечер.

Год Событие
1958 Маккарти изобретает Lisp в MIT
1975 Сассман и Стил создают Scheme
1984 Стандартизирован Common Lisp
1995 Рождается PLT Scheme (будущий Racket)
2007 Появляется Clojure (Lisp на JVM)
2010 PLT Scheme переименован в Racket
Сегодня Читатель этой статьи, готовый писать скобки UwU

Кто использует Lisp сегодня?

Больше людей, чем можно подумать:

  • Clojure работает в продакшене банков, авиакомпаний и стартапов (Nubank, крупнейший цифровой банк Латинской Америки, построен на Clojure).
  • Common Lisp (с компилятором SBCL) до сих пор применяется в экспертных системах, планировании авиаперелётов (ITA Software, приобретённая Google, лежала в основе Google Flights) и научных вычислениях.
  • Emacs Lisp — миллионы людей ежедневно запускают Lisp, даже не подозревая об этом, ведь их редактор сам является интерпретатором Lisp.
  • Guile/Guix — целый дистрибутив Linux, настраиваемый на 100% через Scheme.
  • Racket имеет собственную ежегодную конференцию (RacketCon), активное академическое и творческое сообщество и применяется для исследования языков, формальной верификации (Rosette), типографики и издательского дела (Pollen), а также в образовании по всему миру.
  • Новые лиспы продолжают появляться: Fennel (Lisp, компилирующийся в Lua, популярен в геймдеве), Janet, Hy (Lisp поверх Python)...

Пасхалка для фанатов TADC

TADC

В сериале The Amazing Digital Circus (эпизод 8, «hjsakldfhl»), когда Кингер открывает терминал, пытаясь перезагрузить Кейна, видно, что Кейн (креативный ИИ, созданный в 1996 году) написан на Lisp. Файл буквально называется Caine-core.lisp.

Установка (5 минут)

  1. Перейти на https://racket-lang.org
  2. Скачать установщик для своей системы (Linux, macOS, Windows).
  3. Открыть DrRacket — среду, идущую в комплекте.

В DrRacket две области: сверху пишутся определения (сама программа), а снизу находится REPL для интерактивных экспериментов. На первой строке области определений нужно написать:

#lang racket

Эта строка сообщает Racket, какой язык используется (стоит помнить: Racket — фабрика языков, поэтому нужно выбрать конкретный).

Тем, кто предпочитает терминал: команда racket запускает REPL, а raco — менеджер пакетов и набор инструментов.

Первый контакт: всё является выражением

В REPL можно попробовать:

> (+ 1 2)
3
> (* 3 (+ 2 2))
12
> (string-append "hello " "world")
"hello world"

Правило Lisp умещается в одну строку:

Всё — это (оператор аргумент1 аргумент2 ...). Всегда. Без исключений.

Не нужно запоминать приоритет операторов, нет особого синтаксиса ни для чего. (+ 1 2) складывает. (if ...) решает. (define ...) называет. Скобки, которые поначалу пугают, на деле означают полное отсутствие произвольных правил. Через неделю их перестаёшь замечать.

Определения и функции

#lang racket

(define pi-approx 3.14159)

(define (circle-area r)
  (* pi-approx r r))

(circle-area 2)   ; => 12.56636
  • define с именем создаёт константу.
  • define с (имя аргументы...) создаёт функцию.
  • Комментарии начинаются с ;.

Анонимные функции используют lambda (да, та самая лямбда — лямбда-исчисление Чёрча из 1930-х годов является теоретическим предком всего этого):

(lambda (x) (* x x))          ; функция без имени
((lambda (x) (* x x)) 5)      ; => 25, применена сразу

Списки: сердце Lisp

Lisp расшифровывается как LISt Processing — обработка списков. Списки — фундаментальная структура языка:

(list 1 2 3)          ; => '(1 2 3)
'(1 2 3)              ; то же самое, "закавыченное"
(first '(1 2 3))      ; => 1
(rest '(1 2 3))       ; => '(2 3)
(cons 0 '(1 2 3))     ; => '(0 1 2 3)
(length '(a b c))     ; => 3

Обратите внимание на кавычку '. Она говорит Racket: не вычисляй это, это данные. Эта деталь — дверь к финальному трюку.

Функции высшего порядка

Здесь Racket раскрывается по-настоящему. Передача функций другим функциям — самая естественная вещь на свете:

(map (lambda (x) (* x x)) '(1 2 3 4 5))
; => '(1 4 9 16 25)

(filter even? '(1 2 3 4 5 6))
; => '(2 4 6)

(foldl + 0 '(1 2 3 4 5))
; => 15

map преобразует, filter отбирает, foldl накапливает. С этими тремя функциями можно решить большинство задач со списками, не написав ни одного цикла for.

Рекурсия: мышление по спирали

В Lisp не думают «повторить N раз» — думают «какой базовый случай, и как к нему приблизиться?»:

(define (factorial n)
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))

(factorial 5)   ; => 120

Для наглядности стоит что-нибудь нарисовать — Racket поставляется со встроенными графическими библиотеками:

#lang racket
(require 2htdp/image)

(define (sierpinski level)
  (if (= level 0)
      (triangle 8 "solid" "purple")
      (let ([t (sierpinski (- level 1))])
        (above t (beside t t)))))

(sierpinski 6)

Достаточно вставить это в DrRacket, нажать Run — и на экране появится треугольник Серпинского.

Финальный аккорд: код, который пишет код

Вспомним кавычку ' — она превращает код в данные. Смотрите:

'(+ 1 2)          ; => СПИСОК (+ 1 2), а не число 3
(first '(+ 1 2))  ; => символ +
(eval '(+ 1 2))   ; => 3. Данные только что были выполнены как код.

Программа — это список. Списки можно строить. Значит: можно строить программы с помощью программ. Это и есть гомоиконичность, и именно поэтому в Lisp существуют настоящие макросы — не текстовые макросы, как в C, а функции, которые принимают код и возвращают код ещё до того, как что-либо выполнится.

В Racket нет цикла while? Можно изобрести его:

(define-syntax-rule (while condition body ...)
  (let loop ()
    (when condition
      body ...
      (loop))))

(define counter 0)
(while (< counter 5)
  (displayln counter)
  (set! counter (+ counter 1)))

Это и есть расширение языка! В Lisp синтаксис принадлежит программисту.

Алан Кэй называл Lisp «уравнениями Максвелла для софта» — крошечным ядром, из которого можно вывести всё остальное.

Что дальше?

Kurosawa_Ruby_Holding_SICP

  • How to Design Programs — книга, вокруг которой был спроектирован Racket, доступна бесплатно онлайн.
  • The Racket Guide — официальная документация, одна из лучших в своём роде.
  • Beautiful Racket — как создавать собственные языки.
  • SICP — классика классик, для тех, кто хочет полного просветления.