Clojure — это современный диалект языка Lisp, работающий на платформе JVM. Это функциональный язык с акцентом на иммутабельность данных и параллельное программирование.
;; Это комментарий
;; Определение функции
(defn hello [name]
(println "Привет," name))
;; Вызов функции
(hello "мир")
;; Числа
42 ;; целое число
3.14 ;; число с плавающей точкой
22/7 ;; рациональное число
1N ;; BigInt
1.0M ;; BigDecimal
;; Строки
"Привет, мир"
;; Символы
'symbol
:keyword ;; ключевое слово (часто используется как ключи в словарях)
;; Булевы значения
true
false
nil ;; эквивалент null
;; Списки (связанные списки)
'(1 2 3 4)
(list 1 2 3 4)
;; Векторы (массивы)
[1 2 3 4]
(vector 1 2 3 4)
;; Словари (ассоциативные массивы)
{:name "Иван" :age 30}
(hash-map :name "Иван" :age 30)
;; Множества
#{1 2 3 4}
(hash-set 1 2 3 4)
;; Простая функция
(defn square [x]
(* x x))
;; Функция с несколькими аргументами
(defn add [a b]
(+ a b))
;; Функция с переменным числом аргументов
(defn sum [& numbers]
(apply + numbers))
;; Анонимная функция
(fn [x] (* x x))
#(* % %) ;; краткая форма
;; if
(if (> x 0)
"Положительное"
"Отрицательное или ноль")
;; when (if без else)
(when (> x 0)
(println "x положительное")
x)
;; cond (множественные условия)
(cond
(< x 0) "Отрицательное"
(> x 0) "Положительное"
:else "Ноль")
;; case (аналог switch)
(case day
"Понедельник" "Начало недели"
"Пятница" "Конец рабочей недели"
"Другой день")
;; doseq (для побочных эффектов)
(doseq [x [1 2 3]]
(println x))
;; for (для создания последовательностей)
(for [x [1 2 3]
y [4 5 6]]
[x y])
;; loop/recur (хвостовая рекурсия)
(loop [x 10]
(when (> x 0)
(println x)
(recur (dec x))))
Thread-first (->
) и Thread-last (->>
) макросы позволяют писать более читаемый код, избегая глубокой вложенности выражений.
Thread-first макрос вставляет каждое выражение как первый аргумент следующей формы.
;; Без thread-first
(+ (* (- 10 3) 2) 5) ;; => 19
;; С thread-first
(-> 10
(- 3)
(* 2)
(+ 5)) ;; => 19
;; Эквивалентно:
;; (+ (* (- 10 3) 2) 5)
;; Пример с функциями для работы со словарями
(-> {:name "Иван" :age 30}
(assoc :city "Москва")
(update :age inc)
(dissoc :name)) ;; => {:age 31 :city "Москва"}
;; Эквивалентно:
;; (dissoc (update (assoc {:name "Иван" :age 30} :city "Москва") :age inc) :name)
Thread-last макрос вставляет каждое выражение как последний аргумент следующей формы.
;; Без thread-last
(reduce + (filter even? (map #(* % %) (range 10)))) ;; => 120
;; С thread-last
(->> (range 10)
(map #(* % %))
(filter even?)
(reduce +)) ;; => 120
;; Эквивалентно:
;; (reduce + (filter even? (map #(* % %) (range 10))))
;; Пример обработки данных
(->> [{:name "Иван" :age 30}
{:name "Мария" :age 25}
{:name "Алексей" :age 40}]
(filter #(> (:age %) 25))
(map :name)
(clojure.string/join ", ")) ;; => "Иван, Алексей"
;; Комбинирование -> и ->>
(-> {:users [{:name "Иван" :age 30}
{:name "Мария" :age 25}
{:name "Алексей" :age 40}]}
(update :users
#(->> %
(filter (fn [user] (> (:age user) 25)))
(map :name)))) ;; => {:users ("Иван" "Алексей")}
;; as-> (для произвольного позиционирования аргумента)
(as-> (range 10) x
(map #(* % %) x)
(filter even? x)
(reduce + x)
(str "Сумма квадратов чётных чисел: " x))
;; => "Сумма квадратов чётных чисел: 120"
;; some-> (короткое замыкание при nil)
(some-> {:user {:name "Иван"}}
:user
:age
inc) ;; => nil (т.к. :age отсутствует)
;; cond-> (условное применение функций)
(cond-> 10
true (+ 5)
false (* 2)
(> 10 5) (- 3)) ;; => 12
;; map (применяет функцию к каждому элементу)
(map inc [1 2 3 4]) ;; => (2 3 4 5)
;; filter (отбирает элементы по условию)
(filter even? [1 2 3 4]) ;; => (2 4)
;; reduce (сворачивает коллекцию)
(reduce + [1 2 3 4]) ;; => 10
;; first, rest, last
(first [1 2 3]) ;; => 1
(rest [1 2 3]) ;; => (2 3)
(last [1 2 3]) ;; => 3
;; conj (добавляет элементы)
(conj [1 2] 3) ;; => [1 2 3]
(conj '(2 3) 1) ;; => (1 2 3)
;; get
(get {:a 1 :b 2} :a) ;; => 1
(get {:a 1 :b 2} :c) ;; => nil
(get {:a 1 :b 2} :c 0) ;; => 0 (с дефолтным значением)
;; assoc (добавляет или обновляет пары ключ-значение)
(assoc {:a 1} :b 2) ;; => {:a 1 :b 2}
;; dissoc (удаляет пары ключ-значение)
(dissoc {:a 1 :b 2} :a) ;; => {:b 2}
;; keys, vals
(keys {:a 1 :b 2}) ;; => (:a :b)
(vals {:a 1 :b 2}) ;; => (1 2)
Деструктуризация - мощный механизм для извлечения значений из структур данных.
;; Базовая деструктуризация вектора
(let [[a b c] [1 2 3]]
(println a b c)) ;; => 1 2 3
;; С остаточным параметром
(let [[first second & rest] [1 2 3 4 5]]
(println first) ;; => 1
(println second) ;; => 2
(println rest)) ;; => (3 4 5)
;; Пропуск элементов
(let [[a _ c] [1 2 3]]
(println a c)) ;; => 1 3
;; Вложенная деструктуризация
(let [[[a b] [c d]] [[1 2] [3 4]]]
(println a b c d)) ;; => 1 2 3 4
;; Базовая деструктуризация словаря
(let [{name :name age :age} {:name "Иван" :age 30}]
(println name age)) ;; => Иван 30
;; Использование :keys
(let [{:keys [name age]} {:name "Иван" :age 30}]
(println name age)) ;; => Иван 30
;; Использование :strs (для строковых ключей)
(let [{:strs [name age]} {"name" "Иван" "age" 30}]
(println name age)) ;; => Иван 30
;; Использование :syms (для символьных ключей)
(let [{:syms [name age]} {'name "Иван" 'age 30}]
(println name age)) ;; => Иван 30
;; Значения по умолчанию с :or
(let [{:keys [name age] :or {age 25}} {:name "Иван"}]
(println name age)) ;; => Иван 25
;; Связывание всего словаря с :as
(let [{:keys [name] :as person} {:name "Иван" :age 30}]
(println name)
(println person)) ;; => Иван {:name "Иван" :age 30}
;; Деструктуризация вектора
(defn process-point [[x y]]
(println "Координаты:" x y))
(process-point [10 20]) ;; => Координаты: 10 20
;; Деструктуризация словаря
(defn greet-person [{:keys [name age]}]
(println "Привет," name "! Тебе" age "лет."))
(greet-person {:name "Иван" :age 30}) ;; => Привет, Иван! Тебе 30 лет.
Ключевые слова - это специальные символы, начинающиеся с двоеточия. Они самоэвалуируются и часто используются как ключи в словарях.
;; Определение ключевых слов
:name
:age
:user/name ;; с пространством имён
;; Использование как функций для получения значений из словарей
(:name {:name "Иван" :age 30}) ;; => "Иван"
(:address {:name "Иван"} "Не указан") ;; => "Не указан" (со значением по умолчанию)
;; Сравнение с использованием get
(get {:name "Иван" :age 30} :name) ;; => "Иван"
;; Ключевые слова в функциях
(defn configure [options]
(let [debug? (:debug options false)
level (:level options :info)]
(println "Debug:" debug? "Level:" level)))
(configure {:debug true}) ;; => Debug: true Level: :info
;; Определение пространства имён
(ns my-app.core)
;; Импорт Clojure библиотек
(ns my-app.core
(:require [clojure.string :as str]))
;; Импорт Java классов
(ns my-app.core
(:import [java.util Date]))
;; Использование
(str/upper-case "hello") ;; => "HELLO"
(Date.) ;; создаёт новый экземпляр Date
Атомы обеспечивают атомарное обновление значений без необходимости координации.
;; Создание атома
(def counter (atom 0))
;; Получение значения (разыменование)
@counter ;; => 0
;; Установка нового значения
(reset! counter 100) ;; => 100
;; Атомарное обновление
(swap! counter inc) ;; => 101
(swap! counter + 10) ;; => 111
;; Условное обновление
(swap! counter (fn [current]
(if (> current 200)
current
(+ current 50))))
;; Наблюдатели (watches)
(add-watch counter :logger
(fn [key atom old-state new-state]
(println "Значение изменилось с" old-state "на" new-state)))
;; Валидаторы
(def positive (atom 1 :validator pos?))
(reset! positive 5) ;; работает
;; (reset! positive -5) ;; выбросит исключение
Refs используются для координированных изменений нескольких ссылок внутри транзакций.
;; Создание ссылок
(def account1 (ref 1000))
(def account2 (ref 500))
;; Чтение значений
@account1 ;; => 1000
@account2 ;; => 500
;; Транзакция с изменением нескольких ссылок
(dosync
(alter account1 - 200)
(alter account2 + 200))
;; После транзакции
@account1 ;; => 800
@account2 ;; => 700
;; Использование commute (для коммутативных операций)
(dosync
(commute account1 + 100)
(commute account2 + 50))
;; Использование ensure для блокировки без изменения
(dosync
(ensure account1) ;; блокирует, но не меняет
(when (> @account1 500)
(alter account2 + 100)))
;; Валидаторы
(def balance (ref 1000 :validator #(>= % 0)))
;; (dosync (alter balance - 2000)) ;; выбросит исключение
Агенты обеспечивают асинхронное изменение состояния с гарантией сериализации действий.
;; Создание агента
(def counter (agent 0))
;; Отправка действий (асинхронно)
(send counter inc)
(send counter + 10)
;; Блокирующее ожидание завершения всех действий
(await counter)
@counter ;; => 11
;; Отправка действий, которые могут выбросить исключения
(send-off counter (fn [_] (Thread/sleep 1000) 42))
;; Обработка ошибок
(agent-error counter) ;; возвращает исключение или nil
(restart-agent counter 0) ;; перезапускает агент с новым значением
;; Режим обработки ошибок
(set-error-handler! counter (fn [agent exception]
(println "Ошибка:" exception)))
(set-error-mode! counter :continue) ;; продолжать работу при ошибках
Vars - это ссылки на значения в пространстве имён.
;; Определение var
(def x 10)
;; Динамические vars
(def ^:dynamic *debug* false)
;; Временное изменение значения динамической переменной
(binding [*debug* true]
(println "Debug mode:" *debug*))
;; После binding значение возвращается к исходному
*debug* ;; => false
;; Потокобезопасные изменения
(alter-var-root #'x inc) ;; => 11
;; Приватные vars
(def ^:private secret "секретное значение")
;; Атомы (атомарные ссылки)
(def counter (atom 0))
(swap! counter inc) ;; атомарно увеличивает значение
(reset! counter 0) ;; устанавливает новое значение
@counter ;; разыменовывает атом
;; Агенты (асинхронные ссылки)
(def scores (agent {}))
(send scores assoc :player1 100)
;; Refs (для координированных транзакций)
(def account1 (ref 1000))
(def account2 (ref 500))
(dosync
(alter account1 - 100)
(alter account2 + 100))
;; Определение макроса
(defmacro when-positive [test & body]
`(when (pos? ~test)
~@body))
;; Использование макроса
(when-positive 5
(println "Положительное число")
(println "Выполняем код"))
;; Макрос для отладки
(defmacro dbg [x]
`(let [x# ~x]
(println "debug:" '~x "=" x#)
x#))
;; try/catch/finally
(try
(/ 1 0)
(catch ArithmeticException e
(println "Ошибка:" (.getMessage e)))
(finally
(println "Выполняется всегда")))
;; throw
(throw (Exception. "Что-то пошло не так"))
;; Вызов статических методов
(Math/sqrt 16) ;; => 4.0
;; Создание объектов
(new java.util.Date)
(java.util.Date.) ;; эквивалентно
;; Вызов методов
(.toUpperCase "hello") ;; => "HELLO"
;; Доступ к полям
(.-x point) ;; доступ к полю x
Transient коллекции используются для эффективного построения больших коллекций.
;; Создание transient коллекции
(def v (transient []))
;; Добавление элементов
(def v (conj! v 1))
(def v (conj! v 2))
(def v (conj! v 3))
;; Преобразование обратно в персистентную коллекцию
(persistent! v) ;; => [1 2 3]
;; Пример с reduce
(persistent!
(reduce conj! (transient []) (range 1000)))
Метаданные позволяют присоединять дополнительную информацию к символам и коллекциям.
;; Добавление метаданных
(def ^{:doc "Счётчик посещений"} counter (atom 0))
;; Получение метаданных
(meta #'counter) ;; => {:doc "Счётчик посещений"}
;; Метаданные в определениях функций
(defn ^:private square
"Возводит число в квадрат"
[x]
(* x x))
;; Метаданные для типизации
(defn add
^Long [^Long x ^Long y]
(+ x y))
Эта шпаргалка охватывает основные аспекты языка Clojure, включая синтаксис, типы данных, функции, коллекции, деструктуризацию, ключевые слова, thread-first и thread-last макросы, пространства имён, механизмы управления состоянием (atom, ref, agent, var), макросы, обработку исключений, интероперабельность с Java, transient коллекции и метаданные.