LEARN X · ЗА 16 МИН
Clojure
Clojure за 16 минут: весь язык на одной странице через закомментированный код — S-выражения, коллекции, функции, seq, atom, макросы threading.
Clojure — функциональный Lisp на JVM: код — это данные (списки), всё неизменяемо по умолчанию, упор на чистые функции и работу с последовательностями. Весь язык — ниже, прямо в комментариях к коду. Комментарий начинается с ;, результат показан как ; => ...
1. Вывод и комментарии
; Однострочный комментарий начинается с точки с запятой
;; По соглашению комментарий на отдельной строке пишут с двух ;;
(comment "а это форма comment — игнорирует всё внутри")
(println "Привет" "мир") ; печатает с пробелом и переводом строки => Привет мир
(print "без перевода строки") ; печатает как есть, без \n
(prn "строка") ; печатает в read-формате, с кавычками => "строка"
(println (str "a" "b" "c")) ; str склеивает в строку => abc
2. Синтаксис S-выражений
Всё — это списки в круглых скобках: первый элемент — функция/оператор, остальное — аргументы (префиксная запись).
(+ 1 2) ; => 3 — оператор СНАЧАЛА, потом аргументы
(+ 1 2 3 4) ; => 10 — любое число аргументов
(* 2 (+ 3 4)) ; => 14 — вложенность: внутреннее вычисляется первым
(- 10 3 2) ; => 5
(/ 10 2) ; => 5
(< 1 2 3) ; => true — сравнение цепочкой
; (f a b) вместо привычного f(a, b) — скобки ВОКРУГ всего вызова
(max 3 8 1) ; => 8
3. Базовые типы
42 ; long (целое)
3.14 ; double
1/3 ; ratio — точная дробь, не теряет точность
(+ 1/3 1/6) ; => 1/2
"строка" ; string (в двойных кавычках)
\a ; character — символ через обратный слэш
:keyword ; keyword — самоопределяемая константа, часто как ключ мапы
:user/name ; квалифицированный keyword (с неймспейсом)
true false ; booleans
nil ; отсутствие значения (как null)
'symbol ; symbol — имя; кавычка ' защищает от вычисления
; Только false и nil — ложь; всё остальное (0, "", []) — истина!
(if 0 "истина" "ложь") ; => "истина"
4. Коллекции
Четыре базовые неизменяемые коллекции.
'(1 2 3) ; список (list) — ' защищает от вычисления как вызова
(list 1 2 3) ; => (1 2 3) то же самое
[1 2 3] ; вектор (vector) — индексируемый, доступ по позиции O(1)
(get [10 20 30] 1) ; => 20
([10 20 30] 1) ; => 20 вектор сам работает как функция индекса
{:a 1 :b 2} ; мапа (map) — пары ключ-значение
(get {:a 1 :b 2} :a) ; => 1
({:a 1 :b 2} :a) ; => 1 мапа — функция от ключа
(:a {:a 1 :b 2}) ; => 1 keyword — функция от мапы (идиоматично)
#{1 2 3} ; множество (set) — уникальные элементы
(contains? #{1 2 3} 2) ; => true
(#{1 2 3} 2) ; => 2 set — функция проверки принадлежности
5. Определения: def и let
(def pi 3.14159) ; def — глобальная привязка (var) в неймспейсе
pi ; => 3.14159
; let — локальные привязки, видны только внутри тела let
(let [x 5
y 10]
(+ x y)) ; => 15
; x и y за пределами let не существуют
; привязки в let вычисляются по порядку, можно ссылаться на предыдущие
(let [a 2
b (* a a)] ; b видит a
b) ; => 4
6. Функции
(defn square [x] ; defn — определить именованную функцию
(* x x))
(square 5) ; => 25
; анонимная функция через fn
((fn [x] (* x x)) 6) ; => 36
; короткий синтаксис #() — % это первый аргумент, %1 %2 ... по номерам
(#(* % %) 7) ; => 49
(#(+ %1 %2) 3 4) ; => 7
; multiple arity — несколько вариантов по числу аргументов
(defn greet
([] (greet "гость")) ; 0 аргументов вызывает версию с 1
([name] (str "Привет, " name)))
(greet) ; => "Привет, гость"
(greet "Аня") ; => "Привет, Аня"
; variadic — переменное число аргументов через &
(defn total [& nums] (apply + nums))
(total 1 2 3 4) ; => 10
7. Условия
(if (> 5 3) "да" "нет") ; => "да" if (условие тогда иначе)
(when (pos? 5) ; when — if без ветки else, можно несколько форм
(println "положительное")
:ok) ; => :ok
(cond ; cond — несколько условий по порядку
(< 5 0) "отрицательное"
(= 5 0) "ноль"
:else "положительное") ; :else — всегда истинно => "положительное"
(case 2 ; case — сравнение значения с константами
1 "один"
2 "два"
"другое") ; => "два"
8. Работа с коллекциями
(map inc [1 2 3]) ; => (2 3 4) применить функцию к каждому
(map + [1 2 3] [10 20 30]) ; => (11 22 33) параллельно по нескольким
(filter even? [1 2 3 4 5]) ; => (2 4) оставить подходящие
(reduce + [1 2 3 4]) ; => 10 свернуть в одно значение
(reduce + 100 [1 2 3]) ; => 106 с начальным значением
(conj [1 2] 3) ; => [1 2 3] добавить (в вектор — в конец)
(conj '(1 2) 0) ; => (0 1 2) в список — в начало
(into [] '(1 2 3)) ; => [1 2 3] пересыпать одну коллекцию в другую
(assoc {:a 1} :b 2) ; => {:a 1 :b 2} добавить/заменить ключ
(dissoc {:a 1 :b 2} :a) ; => {:b 2} удалить ключ
(get {:a 1} :z 0) ; => 0 значение по умолчанию, если нет ключа
(update {:n 1} :n inc) ; => {:n 2} применить функцию к значению ключа
9. Последовательности (seq) и ленивость
; seq — единый абстрактный интерфейс над любой коллекцией
(first [1 2 3]) ; => 1
(rest [1 2 3]) ; => (2 3)
(cons 0 [1 2]) ; => (0 1 2) приписать элемент в начало
(range 5) ; => (0 1 2 3 4)
(range 2 8 2) ; => (2 4 6) от 2 до 8 с шагом 2
; ленивые последовательности — вычисляются по требованию, могут быть бесконечны
(take 5 (range)) ; => (0 1 2 3 4) range без аргументов бесконечен!
(take 3 (map #(* % %) (range))) ; => (0 1 4)
(take 4 (iterate #(* 2 %) 1)) ; => (1 2 4 8) повторяем функцию
(drop 2 [1 2 3 4]) ; => (3 4)
10. Деструктуризация
; разбор вектора по позициям
(let [[a b c] [1 2 3]]
(+ a b c)) ; => 6
(let [[head & tail] [1 2 3 4]] ; & собирает остаток
tail) ; => (2 3 4)
; разбор мапы по ключам через :keys
(let [{:keys [x y]} {:x 10 :y 20}]
(+ x y)) ; => 30
; деструктуризация прямо в параметрах функции
(defn dist [{:keys [x y]}] ; функция принимает мапу, разбирает её
(Math/sqrt (+ (* x x) (* y y))))
(dist {:x 3 :y 4}) ; => 5.0
11. Полиморфизм: multimethods и protocols
; multimethod — выбор реализации по результату dispatch-функции
(defmulti area :shape) ; диспетчеризуем по ключу :shape
(defmethod area :circle [s] (* 3.14 (:r s) (:r s)))
(defmethod area :square [s] (* (:side s) (:side s)))
(area {:shape :circle :r 2}) ; => 12.56
(area {:shape :square :side 3}) ; => 9
; protocol — набор функций (как интерфейс), реализуемый для типов
(defprotocol Speaker
(speak [this]))
(defrecord Dog [name]
Speaker
(speak [this] (str (:name this) ": гав")))
(speak (->Dog "Рекс")) ; => "Рекс: гав"
12. Управление состоянием: atom и неизменяемость
Все коллекции неизменяемы. Для управляемого изменяемого состояния — atom.
; обновление коллекции возвращает НОВУЮ, исходная не меняется
(def v [1 2 3])
(conj v 4) ; => [1 2 3 4]
v ; => [1 2 3] оригинал не тронут!
(def counter (atom 0)) ; atom — управляемая изменяемая ссылка
@counter ; => 0 @ разыменовывает (читает значение)
(swap! counter inc) ; => 1 применяет функцию к текущему значению
(swap! counter + 10) ; => 11 swap! может с доп.аргументами
(reset! counter 0) ; => 0 установить значение напрямую
@counter ; => 0
13. Макросы и threading
Threading-макросы убирают вложенность, делая код читаемым слева направо.
; -> (thread-first) подставляет результат ПЕРВЫМ аргументом следующей формы
(-> 5 (+ 3) (* 2)) ; => 16 читается: 5, +3 =8, *2 =16
; то же без макроса: (* (+ 5 3) 2)
(-> {:a 1}
(assoc :b 2)
(assoc :c 3)) ; => {:a 1 :b 2 :c 3}
; ->> (thread-last) подставляет результат ПОСЛЕДНИМ аргументом
(->> [1 2 3 4 5]
(filter even?) ; => (2 4)
(map #(* % %)) ; => (4 16)
(reduce +)) ; => 20
; ->> идеален для пайплайнов над коллекциями (map/filter/reduce)
; макросы — это код, порождающий код (defmacro), на них построен сам язык