LEARN X · ЗА 15 МИН
Scheme
Scheme за 15 минут: весь минималистичный диалект Lisp на одной странице через закомментированный код — S-выражения, рекурсия, замыкания, call/cc.
Scheme — крошечный, но мощный диалект Lisp. Вся программа — это вложенные списки (S-выражения), а синтаксис умещается в пару правил. Ниже — почти весь язык в комментариях кода (R7RS / Racket-совместимо).
Синтаксис: S-выражения
Программа — это списки в круглых скобках с префиксной записью: оператор идёт первым.
; Это комментарий — от точки с запятой до конца строки
#| Блочный комментарий
может занимать несколько строк |#
; Префиксная запись: (оператор аргумент1 аргумент2 ...)
(+ 1 2 3) ; => 6 (то же, что 1 + 2 + 3)
(* 2 (+ 3 4)) ; => 14 (вложенные выражения)
(- 10 3 2) ; => 5
; Вывод на экран
(display "Привет, Scheme!") ; печатает строку
(newline) ; перевод строки
; Любое выражение возвращает значение — отдельных "команд" нет
Переменные: define, let, let*, letrec
; Глобальное связывание именем
(define x 10)
(define name "Алиса")
(display x) ; => 10
; let — локальные переменные (значения вычисляются параллельно)
(let ((a 1)
(b 2))
(+ a b)) ; => 3
; let* — последовательно: b видит a
(let* ((a 1)
(b (+ a 1)))
(+ a b)) ; => 3
; letrec — для взаимно рекурсивных определений
(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1)))))
(odd? (lambda (n) (if (= n 0) #f (even? (- n 1))))))
(even? 10)) ; => #t
Типы данных
; Числа: целые, дроби, вещественные
42 ; целое
1/3 ; точная дробь (rational)
3.14 ; вещественное
(+ 1/2 1/3) ; => 5/6 (точная арифметика)
; Строки
"привет"
(string-length "abc") ; => 3
(string-append "a" "b" "c") ; => "abc"
; Символы (symbols) — атомарные имена, частый "ключ"
'foo ; символ foo
(eq? 'a 'a) ; => #t
; Булевы: только #f ложно, всё остальное (включая 0 и '()) истинно
#t ; истина
#f ; ложь
(< 1 2) ; => #t
(>= 5 5) ; => #t
; Символ-литера (character)
#\a ; символ 'a'
; Пара (cons-ячейка) — базовый строительный блок
(cons 1 2) ; => (1 . 2) точечная пара
Функции: define, lambda, рекурсия
; Анонимная функция
(lambda (x) (* x x))
((lambda (x) (* x x)) 5) ; => 25
; Именованная функция (сахар над define + lambda)
(define (square x)
(* x x))
(square 4) ; => 16
; Несколько аргументов
(define (add a b) (+ a b))
(add 2 3) ; => 5
; Рекурсия — основной способ итерации в Scheme
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
(factorial 5) ; => 120
; Переменное число аргументов
(define (my-list . args) args)
(my-list 1 2 3) ; => (1 2 3)
Условия: if, cond, case, and/or
; if — (if условие тогда иначе)
(if (> 5 3) "больше" "меньше") ; => "больше"
; cond — цепочка условий (аналог if/else if/else)
(define (sign n)
(cond ((> n 0) 'positive)
((< n 0) 'negative)
(else 'zero)))
(sign -7) ; => negative
; case — сравнение значения с вариантами
(define (day-kind d)
(case d
((sat sun) "выходной")
((mon tue wed thu fri) "будни")
(else "неизвестно")))
(day-kind 'sat) ; => "выходной"
; and / or — короткое замыкание, возвращают значение
(and 1 2 3) ; => 3 (последнее истинное)
(or #f #f 'yes) ; => yes (первое истинное)
(and (> 5 1) (< 5 10)) ; => #t
Списки: cons, car, cdr, list, цитирование
Список — это цепочка пар, заканчивающаяся пустым списком '().
; Построение списка
(list 1 2 3) ; => (1 2 3)
(cons 1 (cons 2 (cons 3 '()))) ; => (1 2 3)
; Цитирование ' — берём список как данные, не вычисляем
'(1 2 3) ; => (1 2 3)
'(+ 1 2) ; => (+ 1 2) — это список, а не вызов
; car — голова, cdr — хвост
(car '(1 2 3)) ; => 1
(cdr '(1 2 3)) ; => (2 3)
(cadr '(1 2 3)) ; => 2 (car (cdr ...))
; Полезные операции
(length '(1 2 3)) ; => 3
(append '(1 2) '(3 4)) ; => (1 2 3 4)
(reverse '(1 2 3)) ; => (3 2 1)
(list-ref '(a b c) 1) ; => b
(null? '()) ; => #t (пустой ли список)
Функции высшего порядка
Функции — значения первого класса: их можно передавать и возвращать.
; map — применить функцию к каждому элементу
(map (lambda (x) (* x x)) '(1 2 3 4)) ; => (1 4 9 16)
(map + '(1 2 3) '(10 20 30)) ; => (11 22 33)
; filter — оставить подходящие элементы
(filter even? '(1 2 3 4 5 6)) ; => (2 4 6)
(filter (lambda (x) (> x 2)) '(1 2 3 4)) ; => (3 4)
; fold-left / fold-right — свёртка
(fold-left + 0 '(1 2 3 4)) ; => 10
(fold-right cons '() '(1 2 3)) ; => (1 2 3)
; apply — вызвать функцию со списком аргументов
(apply + '(1 2 3 4)) ; => 10
(apply max '(3 1 4 1 5 9)) ; => 9
Хвостовая рекурсия
Если рекурсивный вызов — последнее действие, Scheme гарантирует оптимизацию: стек не растёт. Это замена циклам.
; НЕ хвостовая: после вызова ещё нужно умножить — стек растёт
(define (fact-slow n)
(if (= n 0) 1 (* n (fact-slow (- n 1)))))
; Хвостовая: вызов loop — самое последнее действие, аккумулятор копит ответ
(define (factorial n)
(define (loop n acc)
(if (= n 0)
acc
(loop (- n 1) (* n acc)))) ; ничего не делаем ПОСЛЕ вызова
(loop n 1))
(factorial 100000) ; работает без переполнения стека
; Так пишут "циклы": считаем сумму от 1 до n
(define (sum-to n)
(let loop ((i 1) (acc 0)) ; named let = удобная хвостовая петля
(if (> i n)
acc
(loop (+ i 1) (+ acc i)))))
(sum-to 100) ; => 5050
Замыкания (closures)
Lambda "захватывает" переменные из окружения, в котором была создана.
; Функция, возвращающая функцию
(define (make-adder n)
(lambda (x) (+ x n))) ; n живёт в замыкании
(define add5 (make-adder 5))
(add5 10) ; => 15
(add5 100) ; => 105
; Приватное изменяемое состояние через замыкание
(define (make-counter)
(let ((count 0))
(lambda ()
(set! count (+ count 1)) ; set! меняет захваченную переменную
count)))
(define next (make-counter))
(next) ; => 1
(next) ; => 2
(next) ; => 3 — count невидим снаружи
Продолжения: call/cc
Уникальная фишка Scheme: call-with-current-continuation (кратко call/cc) даёт первоклассный объект — "что делать дальше". На нём строят ранний выход, исключения, генераторы.
; Простейший случай — ранний выход из вычисления
(call/cc
(lambda (return)
(+ 1 (return 42)))) ; вызвав return, сразу выходим со значением 42
; => 42 (прибавление к 1 не происходит)
; Поиск с досрочным прерыванием
(define (find-first pred lst)
(call/cc
(lambda (break)
(for-each (lambda (x)
(when (pred x) (break x))) ; нашли — выходим
lst)
#f))) ; не нашли
(find-first even? '(1 3 5 6 7)) ; => 6
Особенности и философия
; 1. Минимализм: весь синтаксис — это S-выражения.
; Код и данные имеют одинаковую форму (гомоиконность),
; поэтому макросы могут переписывать программу.
; 2. Всё — выражение и возвращает значение (даже if, let, cond).
(define result
(if (> 2 1) "да" "нет")) ; if вернул значение в define
; 3. Чисто функциональный стиль поощряется,
; но set! и мутации доступны при необходимости.
; 4. eq? / eqv? / equal? — три уровня сравнения
(eq? 'a 'a) ; => #t (идентичность)
(equal? '(1 2) '(1 2)) ; => #t (структурное равенство)
; Дальше: макросы (define-syntax), векторы, хеш-таблицы,
; модули — но ядро языка вы уже видели целиком.