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), векторы, хеш-таблицы,
; модули — но ядро языка вы уже видели целиком.
Поддержать проект