LEARN X · ЗА 15 МИН

Racket

Racket за 15 минут: весь язык на одной странице в комментариях кода — define, lambda, match, struct, list, map, hash, vector, for.

Racket — потомок Scheme и диалект Lisp с богатой экосистемой, мощным сопоставлением с образцом (match) и развитой системой макросов. Весь синтаксис — это списки в круглых скобках: (функция аргумент1 аргумент2). Ниже — весь язык в комментариях рабочего кода.

Начало: #lang, вывод и комментарии

Каждая программа начинается со строки #lang, объявляющей диалект.

#lang racket  ; диалект языка: основной — racket

; Однострочный комментарий начинается с точки с запятой ;

#|
  Многострочный (блочный) комментарий
  заключается между #| и |#
|#

#;(это выражение целиком закомментировано датум-комментарием)

(displayln "Привет, мир!")  ; печать значения + перевод строки
(display "без переноса")    ; печать без перевода строки
(newline)                   ; явный перевод строки
(print "со скобками строка"); печать в read-форме (с кавычками)

; printf — форматированный вывод; ~a — человекочитаемо, ~v — read-форма
(printf "Сумма ~a и ~a равна ~a\n" 2 3 (+ 2 3))  ; Сумма 2 и 3 равна 5

Переменные: define, let, let*

define создаёт глобальное (или модульное) имя, let — локальное.

(define x 10)        ; связываем имя x со значением 10
(define имя "Аня")   ; имена могут быть на кириллице
(define pi 3.14159)

; let — локальные привязки; видны только внутри тела
(let ([a 1]
      [b 2])
  (+ a b))           ; => 3, a и b снаружи не видны

; let* — последующие привязки видят предыдущие
(let* ([a 5]
       [b (* a 2)])  ; b может ссылаться на уже объявленное a
  (+ a b))           ; => 15

; 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

; set! изменяет уже существующую привязку (мутация)
(define счётчик 0)
(set! счётчик (+ счётчик 1))  ; счётчик теперь 1

Типы: числа, строки, символы, булевы

; --- Числа: точные и неточные ---
42                  ; целое (exact integer)
(/ 1 3)             ; => 1/3 — точная дробь (рациональное число!)
3.14                ; неточное (inexact, число с плавающей точкой)
(exact->inexact 1/3); => 0.3333333333333333
(inexact->exact 0.5); => 1/2
1+2i                ; комплексное число
(expt 2 100)        ; => огромное целое (произвольная точность)

; Арифметика — префиксная: оператор идёт первым
(+ 1 2 3)           ; => 6 (любое число аргументов)
(- 10 3 2)          ; => 5
(* 2 3 4)           ; => 24
(quotient 17 5)     ; => 3 (целочисленное деление)
(remainder 17 5)    ; => 2 (остаток)
(modulo -7 3)       ; => 2

; --- Строки ---
"строка в двойных кавычках"
(string-append "Привет, " "Racket")    ; => "Привет, Racket"
(string-length "абвг")                 ; => 4
(substring "Racket" 0 3)               ; => "Rac"
(string-upcase "racket")               ; => "RACKET"
(string->number "42")                  ; => 42
(number->string 42)                    ; => "42"
(format "~a + ~a" 2 3)                 ; => "2 + 3"

; --- Символы (symbols): легковесные интернированные имена ---
'привет             ; символ привет (с апострофом — не вычисляется)
(symbol->string 'abc); => "abc"
(eq? 'a 'a)         ; => #t (символы сравниваются мгновенно)

; --- Булевы значения ---
#t                  ; истина (можно писать #true)
#f                  ; ложь (можно писать #false)
; ВАЖНО: ложно ТОЛЬКО #f, всё остальное (0, "", '()) — истинно!
(and #t #t #f)      ; => #f
(or  #f #f #t)      ; => #t
(not #f)            ; => #t

Функции: define, lambda, аргументы

; Определение функции — короткая форма
(define (квадрат x)
  (* x x))
(квадрат 5)         ; => 25

; lambda — анонимная функция
(define куб (lambda (x) (* x x x)))
(куб 3)             ; => 27
((lambda (x) (+ x 1)) 41)  ; => 42 (вызов на месте)

; Переменное число аргументов: . собирает остаток в список
(define (сумма-всех . числа)
  (apply + числа))
(сумма-всех 1 2 3 4)        ; => 10

; Аргументы по умолчанию (необязательные)
(define (приветствие имя [восклицание "!"])
  (string-append "Привет, " имя восклицание))
(приветствие "Аня")        ; => "Привет, Аня!"
(приветствие "Аня" "?")    ; => "Привет, Аня?"

; Именованные (keyword) аргументы — через #:
(define (точка #:x x #:y y)
  (list x y))
(точка #:y 2 #:x 1)        ; => '(1 2) (порядок не важен)

; Каррирование: функция, возвращающая функцию
(define ((прибавить n) m) (+ n m))
(define прибавить5 (прибавить 5))
(прибавить5 10)            ; => 15
((прибавить 3) 4)         ; => 7

; curry из стандартной библиотеки
(define inc (curry + 1))
(inc 9)                   ; => 10

Условия: if, cond, when, unless, case

; if — ровно две ветви: (if УСЛОВИЕ ТО ИНАЧЕ)
(if (> 5 3) "больше" "меньше")   ; => "больше"

; cond — цепочка условий, else в конце
(define (знак n)
  (cond
    [(> n 0) "положительное"]
    [(< n 0) "отрицательное"]
    [else    "ноль"]))
(знак -7)                        ; => "отрицательное"

; cond с => передаёт результат теста в функцию
(cond
  [(member 2 '(1 2 3)) => (lambda (rest) (car rest))]  ; => 2
  [else 'нет])

; when — выполнить тело, ЕСЛИ условие истинно (без ветви else)
(when (> 10 5)
  (displayln "десять больше пяти"))

; unless — выполнить, ЕСЛИ условие ЛОЖНО
(unless (= 1 2)
  (displayln "один не равен двум"))

; case — сравнение значения по веткам (через equal?)
(define (день-недели n)
  (case n
    [(1 2 3 4 5) "будни"]
    [(6 7)       "выходные"]
    [else        "нет такого дня"]))
(день-недели 6)                  ; => "выходные"

Списки: cons, list, first/rest, кавычка

Список — основа Lisp: цепочка пар (cons-ячеек), заканчивающаяся пустым списком '().

'(1 2 3)            ; литерал списка (апостроф = quote, не вычислять)
(list 1 2 3)        ; то же самое, но аргументы вычисляются
(list (+ 1 1) (* 2 2)) ; => '(2 4)
'()                 ; пустой список
empty               ; тоже пустой список

; cons добавляет элемент в начало (строит пару)
(cons 0 '(1 2 3))   ; => '(0 1 2 3)
(cons 1 2)          ; => '(1 . 2) — точечная пара (не список)

; Доступ к элементам
(first '(1 2 3))    ; => 1   (синоним car)
(rest  '(1 2 3))    ; => '(2 3) (синоним cdr)
(second '(1 2 3))   ; => 2
(last '(1 2 3))     ; => 3
(list-ref '(a b c) 1) ; => 'b (по индексу)
(length '(1 2 3))   ; => 3

; Операции со списками
(append '(1 2) '(3 4))   ; => '(1 2 3 4)
(reverse '(1 2 3))       ; => '(3 2 1)
(member 2 '(1 2 3))      ; => '(2 3) (хвост от найденного, или #f)
(range 0 5)              ; => '(0 1 2 3 4)
(list->string '(#\h #\i)); => "hi"
(empty? '())             ; => #t
(null? '())              ; => #t

Функции высшего порядка: map, filter, foldl/foldr, for/list

; 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)

; foldl / foldr — свёртка списка в одно значение
; (foldl ФУНКЦИЯ НАЧАЛО СПИСОК); аккумулятор идёт ВТОРЫМ аргументом
(foldl + 0 '(1 2 3 4))                 ; => 10
(foldl cons '() '(1 2 3))              ; => '(3 2 1) (foldl разворачивает)
(foldr cons '() '(1 2 3))              ; => '(1 2 3) (foldr сохраняет порядок)

; apply — раскрыть список в аргументы функции
(apply max '(3 1 4 1 5 9))             ; => 9

; for/list — генератор списка через итерацию
(for/list ([x (range 1 6)])
  (* x x))                             ; => '(1 4 9 16 25)

; with условием (#:when)
(for/list ([x (range 1 11)] #:when (even? x))
  x)                                   ; => '(2 4 6 8 10)

; Композиция функций
((compose1 add1 (lambda (x) (* x 2))) 5) ; => 11  (5*2, потом +1)

Сопоставление с образцом: match

match — одна из самых мощных фишек Racket: разбор структуры данных по образцам.

; Базовое сопоставление по значениям
(define (описать x)
  (match x
    [0 "ноль"]
    [1 "один"]
    [_ "что-то другое"]))   ; _ — образец-джокер (любое значение)
(описать 1)                  ; => "один"

; Разбор списков: связываем переменные из структуры
(match '(1 2 3)
  [(list a b c) (+ a b c)])  ; => 6 (a=1, b=2, c=3)

; Хвост списка через многоточие ...
(match '(1 2 3 4 5)
  [(list first rest ...) rest]) ; => '(2 3 4 5)

; cons-образец: голова и хвост
(match '(10 20 30)
  [(cons head tail) (list head tail)]) ; => '(10 (20 30))

; Сторожевые условия (#:when) и литералы
(define (классифицировать n)
  (match n
    [(? negative?) "отрицательное"]
    [0 "ноль"]
    [(? even?) "чётное положительное"]
    [_ "нечётное положительное"]))
(классифицировать 4)         ; => "чётное положительное"

; Сопоставление со структурами и захват целого через образец
(match (list 1 2)
  [(list (and whole all) ...) whole]) ; => '(1 2)

Структуры: struct

; struct определяет новый составной тип
(struct точка (x y) #:transparent)
; #:transparent — чтобы значение красиво печаталось и сравнивалось equal?

(define p (точка 3 4))   ; конструктор: имя структуры
(точка-x p)              ; => 3 (автоматический аксессор имя-поле)
(точка-y p)              ; => 4
(точка? p)              ; => #t (предикат имя?)

; Структуры можно разбирать через match
(match p
  [(точка x y) (sqrt (+ (* x x) (* y y)))])  ; => 5 (длина вектора)

; Наследование структур
(struct цветная-точка точка (цвет) #:transparent)
(define cp (цветная-точка 1 2 'красный))
(точка-x cp)            ; => 1 (унаследованное поле)
(цветная-точка-цвет cp) ; => 'красный

; Изменяемые поля через #:mutable
(struct ячейка (значение) #:mutable)
(define c (ячейка 10))
(set-ячейка-значение! c 99)
(ячейка-значение c)     ; => 99

Хеши и векторы

; --- Хеш-таблицы (словари) ---
(define h (hash 'a 1 'b 2 'c 3))   ; неизменяемый хеш
(hash-ref h 'a)                    ; => 1
(hash-ref h 'z 0)                  ; => 0 (значение по умолчанию)
(hash-set h 'd 4)                  ; => новый хеш с добавленной парой
(hash-keys h)                      ; => '(a b c) (порядок не гарантирован)
(hash-values h)                    ; => '(1 2 3)
(hash-has-key? h 'b)               ; => #t
(hash-count h)                     ; => 3

; Изменяемый хеш
(define mh (make-hash))
(hash-set! mh 'ключ 42)
(hash-ref mh 'ключ)                ; => 42

; Перебор хеша
(for ([(k v) (in-hash h)])
  (printf "~a => ~a\n" k v))

; --- Векторы: массивы с доступом по индексу за O(1) ---
(define v (vector 10 20 30))
(vector-ref v 0)                   ; => 10
(vector-length v)                  ; => 3
(define mv (make-vector 5 0))      ; вектор из 5 нулей
(vector-set! mv 2 99)              ; изменяем элемент по индексу
(vector-ref mv 2)                  ; => 99
(vector->list v)                   ; => '(10 20 30)
(list->vector '(1 2 3))            ; => #(1 2 3)

Итерации: for, for/list, for/sum

; for — цикл с побочными эффектами (ничего не возвращает полезного)
(for ([i (range 3)])
  (displayln i))          ; печатает 0, 1, 2

; Несколько последовательностей идут параллельно
(for ([x '(a b c)]
      [i (range 3)])
  (printf "~a: ~a\n" i x)) ; 0: a / 1: b / 2: c

; for/list — собрать результаты в список
(for/list ([i (range 5)]) (* i 10)) ; => '(0 10 20 30 40)

; for/sum — сумма всех значений тела
(for/sum ([i (range 1 101)]) i)     ; => 5050

; for/product — произведение
(for/product ([i (range 1 6)]) i)   ; => 120 (факториал 5)

; for/and, for/or — логическая свёртка
(for/and ([x '(2 4 6)]) (even? x))  ; => #t

; for/hash — построить хеш в цикле
(for/hash ([i (range 3)])
  (values i (* i i)))               ; => хеш {0:0, 1:1, 2:4}

; Вложенные итерации: for* (декартово произведение)
(for*/list ([x '(1 2)] [y '(a b)])
  (list x y))            ; => '((1 a) (1 b) (2 a) (2 b))

Особенности: контракты и типизированный Racket

Racket — это «язык для создания языков». Помимо проверок во время выполнения, есть отдельный диалект со статической типизацией.

; --- Контракты: проверки на границе модуля ---
(require racket/contract)

; provide/contract гарантирует тип/условие при экспорте
(define (удвоить n) (* 2 n))
(provide
  (contract-out
    [удвоить (-> number? number?)]))  ; принимает число — возвращает число

; Контракт можно навесить локально
(define/contract (корень x)
  (-> (and/c real? (not/c negative?)) real?)  ; вход: неотрицательное вещественное
  (sqrt x))
(корень 16)             ; => 4
; (корень -1) — нарушение контракта, понятная ошибка

; --- Типизированный Racket (отдельный #lang) ---
; В отдельном файле первой строкой пишут:
;   #lang typed/racket
; и аннотируют типы:
;
;   (: площадь (-> Real Real Real))
;   (define (площадь ширина высота)
;     (* ширина высота))
;
; Типы проверяются статически, ДО запуска программы.

; --- Бонус: макросы делают язык расширяемым ---
(define-syntax-rule (свой-if усл то иначе)
  (cond [усл то] [else иначе]))
(свой-if (> 2 1) 'да 'нет)  ; => 'да — мы добавили новую форму в язык!
Поддержать проект