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), на них построен сам язык
Поддержать проект