Skip to content

Instantly share code, notes, and snippets.

@dmitry-osin
Created May 21, 2025 21:24
Show Gist options
  • Save dmitry-osin/bae7505defddef509756432e50983ce5 to your computer and use it in GitHub Desktop.
Save dmitry-osin/bae7505defddef509756432e50983ce5 to your computer and use it in GitHub Desktop.
Шпаргалка по языку программирования Clojure

Полная шпаргалка по языку программирования Clojure

Основы Clojure

Что такое Clojure?

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-last (->>) макросы позволяют писать более читаемый код, избегая глубокой вложенности выражений.

Thread-first макрос (->)

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 макрос вставляет каждое выражение как последний аргумент следующей формы.

;; Без 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 ", "))  ;; => "Иван, Алексей"

Комбинирование thread-first и thread-last

;; Комбинирование -> и ->>
(-> {:users [{:name "Иван" :age 30}
             {:name "Мария" :age 25}
             {:name "Алексей" :age 40}]}
    (update :users 
            #(->> %
                  (filter (fn [user] (> (:age user) 25)))
                  (map :name))))  ;; => {:users ("Иван" "Алексей")}

Другие threading макросы

;; 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 лет.

Ключевые слова (Keywords)

Ключевые слова - это специальные символы, начинающиеся с двоеточия. Они самоэвалуируются и часто используются как ключи в словарях.

;; Определение ключевых слов
: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

Управление состоянием

Atom (атомы)

Атомы обеспечивают атомарное обновление значений без необходимости координации.

;; Создание атома
(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)  ;; выбросит исключение

Ref (ссылки)

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))  ;; выбросит исключение

Agent (агенты)

Агенты обеспечивают асинхронное изменение состояния с гарантией сериализации действий.

;; Создание агента
(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)  ;; продолжать работу при ошибках

Var (переменные)

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. "Что-то пошло не так"))

Интероперабельность с Java

;; Вызов статических методов
(Math/sqrt 16)  ;; => 4.0

;; Создание объектов
(new java.util.Date)
(java.util.Date.)  ;; эквивалентно

;; Вызов методов
(.toUpperCase "hello")  ;; => "HELLO"

;; Доступ к полям
(.-x point)  ;; доступ к полю x

Transient коллекции

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

Ленивые последовательности

Ленивые последовательности вычисляются только при необходимости, что позволяет эффективно работать с большими или бесконечными коллекциями.

;; Создание ленивой последовательности
(def lazy-seq-example (map inc (range)))

;; Получение первых 10 элементов
(take 10 lazy-seq-example)  ;; => (1 2 3 4 5 6 7 8 9 10)

;; Создание собственной ленивой последовательности
(defn fibonacci []
  ((fn fib [a b]
     (lazy-seq (cons a (fib b (+ a b)))))
   0 1))

;; Получение первых 10 чисел Фибоначчи
(take 10 (fibonacci))  ;; => (0 1 1 2 3 5 8 13 21 34)

;; Функции для работы с ленивыми последовательностями
(def numbers (range 1000000))  ;; ленивая последовательность
(first numbers)  ;; => 0 (вычисляется только первый элемент)
(nth numbers 500000)  ;; => 500000 (вычисляется только до 500001-го элемента)

;; Композиция ленивых операций
(->> (range)
     (filter even?)
     (map #(* % %))
     (take 10))  ;; => (0 4 16 36 64 100 144 196 256 324)

Протоколы и мультиметоды

Протоколы и мультиметоды обеспечивают полиморфизм в Clojure.

Мультиметоды

;; Определение мультиметода с функцией диспетчеризации
(defmulti area :shape)

;; Реализации для разных типов фигур
(defmethod area :rectangle [rect]
  (* (:width rect) (:height rect)))

(defmethod area :circle [circle]
  (* Math/PI (:radius circle) (:radius circle)))

(defmethod area :default [shape]
  (throw (Exception. (str "Неизвестная фигура: " (:shape shape)))))

;; Использование мультиметода
(area {:shape :rectangle :width 10 :height 5})  ;; => 50
(area {:shape :circle :radius 5})  ;; => 78.53981633974483

;; Мультиметод с функцией диспетчеризации по классу
(defmulti class-of class)

(defmethod class-of String [s]
  (str "Это строка: " s))

(defmethod class-of Number [n]
  (str "Это число: " n))

(class-of "hello")  ;; => "Это строка: hello"
(class-of 42)  ;; => "Это число: 42"

Протоколы

;; Определение протокола
(defprotocol Drawable
  (draw [this] "Рисует объект")
  (bounds [this] "Возвращает границы объекта"))

;; Реализация протокола для записи (record)
(defrecord Circle [x y radius]
  Drawable
  (draw [this]
    (println "Рисуем круг в" x y "с радиусом" radius))
  (bounds [this]
    {:min-x (- x radius)
     :min-y (- y radius)
     :max-x (+ x radius)
     :max-y (+ y radius)}))

;; Создание экземпляра записи
(def my-circle (->Circle 10 20 5))

;; Использование методов протокола
(draw my-circle)  ;; => Рисуем круг в 10 20 с радиусом 5
(bounds my-circle)  ;; => {:min-x 5, :min-y 15, :max-x 15, :max-y 25}

;; Расширение существующих типов протоколом
(extend-protocol Drawable
  String
  (draw [s]
    (println "Текст:" s))
  (bounds [s]
    {:width (count s)}))

(draw "Hello")  ;; => Текст: Hello

Transducers

Трансдьюсеры — это преобразователи, которые можно компонировать и применять к различным контекстам обработки данных.

;; Определение трансдьюсеров
(def xform-inc (map inc))
(def xform-even (filter even?))
(def xform-square (map #(* % %)))

;; Композиция трансдьюсеров
(def xform (comp xform-inc xform-even xform-square))

;; Применение к разным контекстам
(into [] xform (range 10))  ;; => [4 16 36 64 100]

(sequence xform (range 10))  ;; => (4 16 36 64 100)

(transduce xform + (range 10))  ;; => 220

;; Создание собственного трансдьюсера
(defn take-even-xf [rf]
  (let [counter (volatile! 0)]
    (fn
      ([] (rf))
      ([result] (rf result))
      ([result input]
       (let [c @counter]
         (vreset! counter (inc c))
         (if (even? c)
           (rf result input)
           result))))))

(into [] (comp (take-even-xf) (map inc)) (range 10))  ;; => [1 3 5 7 9]

Спецификации (clojure.spec)

Спецификации позволяют описывать и валидировать структуру данных.

(require '[clojure.spec.alpha :as s])
(require '[clojure.spec.gen.alpha :as gen])
(require '[clojure.spec.test.alpha :as stest])

;; Определение простых спецификаций
(s/def ::name string?)
(s/def ::age (s/and int? #(>= % 0)))
(s/def ::email (s/and string? #(re-matches #"^[^@]+@[^@]+\.[^@]+$" %)))

;; Составные спецификации
(s/def ::person (s/keys :req-un [::name ::age]
                        :opt-un [::email]))

;; Валидация данных
(s/valid? ::person {:name "Иван" :age 30})  ;; => true
(s/valid? ::person {:name "Иван" :age -5})  ;; => false

;; Объяснение ошибок
(s/explain ::person {:name "Иван" :age -5})
;; => In: [:age] val: -5 fails spec: :user/age predicate: (>= % 0)

;; Генерация тестовых данных
(gen/generate (s/gen ::person))
;; => {:name "X0N07Sef", :age 0}

;; Спецификации функций
(s/fdef sum
  :args (s/cat :numbers (s/coll-of number?))
  :ret number?
  :fn #(= (:ret %) (apply + (-> % :args :numbers))))

(defn sum [numbers]
  (apply + numbers))

;; Инструментирование функций для проверки во время выполнения
(stest/instrument `sum)

;; Генерация тестов на основе спецификаций
(stest/check `sum)

Тестирование

clojure.test

(require '[clojure.test :refer :all])

;; Определение тестов
(deftest test-addition
  (testing "Сложение чисел"
    (is (= 4 (+ 2 2)))
    (is (= 7 (+ 3 4)))))

(deftest test-string-functions
  (testing "Функции для работы со строками"
    (is (= "HELLO" (.toUpperCase "hello")))
    (is (= 5 (count "hello")))))

;; Запуск тестов
(run-tests)

;; Использование фикстур
(use-fixtures :once
  (fn [f]
    (println "Настройка перед всеми тестами")
    (f)
    (println "Очистка после всех тестов")))

(use-fixtures :each
  (fn [f]
    (println "Настройка перед каждым тестом")
    (f)
    (println "Очистка после каждого теста")))

;; Тесты с исключениями
(deftest test-division-by-zero
  (is (thrown? ArithmeticException (/ 1 0))))

;; Тесты с предикатами
(deftest test-numbers
  (is (pos? 5))
  (is (neg? -3))
  (is (zero? 0)))

Другие библиотеки для тестирования

;; expectations
(require '[expectations :refer :all])

(expect 4 (+ 2 2))
(expect #"hello" "hello world")

;; midje
(require '[midje.sweet :refer :all])

(fact "О сложении"
  (+ 2 2) => 4
  (+ 3 4) => 7)

(fact "О строках"
  (.toUpperCase "hello") => "HELLO"
  (count "hello") => 5)

core.async

core.async предоставляет CSP-подобную модель конкурентности с каналами и go-блоками.

(require '[clojure.core.async :as async :refer [chan go go-loop <! >! <!! >!! timeout alt! alts! close!]])

;; Создание канала
(def my-channel (chan))

;; Отправка и получение значений (блокирующие операции)
(>!! my-channel 42)  ;; отправка значения в канал
(def result (<!! my-channel))  ;; получение значения из канала

;; Использование go-блоков для асинхронного выполнения
(go
  (>! my-channel "hello")  ;; неблокирующая отправка
  (println "Отправлено"))

(go
  (println "Получено:" (<! my-channel)))  ;; неблокирующее получение

;; Буферизованные каналы
(def buffered-channel (chan 10))  ;; канал с буфером на 10 элементов

;; Таймауты
(go
  (let [[value channel] (alts! [my-channel (timeout 1000)])]
    (if value
      (println "Получено значение:" value)
      (println "Таймаут"))))

;; Выбор из нескольких каналов
(let [c1 (chan)
      c2 (chan)]
  (go (>! c1 "from c1"))
  (go (>! c2 "from c2"))
  
  (go
    (alt!
      c1 ([v] (println "Got" v))
      c2 ([v] (println "Got" v)))))

;; Бесконечный цикл обработки сообщений
(go-loop []
  (when-let [msg (<! my-channel)]
    (println "Обработка:" msg)
    (recur)))

;; Закрытие канала
(close! my-channel)

Работа с базами данных

next.jdbc

(require '[next.jdbc :as jdbc])
(require '[next.jdbc.sql :as sql])

;; Создание соединения
(def db-spec {:dbtype "postgresql"
              :dbname "mydb"
              :user "user"
              :password "password"})

(def datasource (jdbc/get-datasource db-spec))

;; Выполнение запросов
(jdbc/execute! datasource ["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT)"])

;; Вставка данных
(sql/insert! datasource :users {:name "Иван" :email "[email protected]"})

;; Запросы с параметрами
(jdbc/execute! datasource ["SELECT * FROM users WHERE name = ?" "Иван"])

;; Получение результатов
(def users (sql/query datasource ["SELECT * FROM users"]))

;; Обновление данных
(sql/update! datasource :users {:email "[email protected]"} {:name "Иван"})

;; Удаление данных
(sql/delete! datasource :users {:name "Иван"})

;; Транзакции
(jdbc/with-transaction [tx datasource]
  (sql/insert! tx :users {:name "Мария" :email "[email protected]"})
  (sql/update! tx :users {:email "[email protected]"} {:name "Иван"}))

HoneySQL

(require '[honey.sql :as sql])
(require '[next.jdbc :as jdbc])

;; Формирование SQL-запросов
(def query
  (sql/format
    {:select [:id :name :email]
     :from [:users]
     :where [:= :name "Иван"]}))

;; => ["SELECT id, name, email FROM users WHERE name = ?" "Иван"]

;; Выполнение сформированного запроса
(jdbc/execute! datasource query)

;; Более сложные запросы
(sql/format
  {:select [:u.id :u.name [:o.id :order_id] :o.amount]
   :from [[:users :u]]
   :join [[:orders :o] [:= :u.id :o.user_id]]
   :where [:and
           [:>= :o.amount 100]
           [:like :u.email "%@example.com"]]
   :order-by [[:o.amount :desc]]
   :limit 10})

Веб-разработка

Ring (HTTP-сервер)

(require '[ring.adapter.jetty :as jetty])
(require '[ring.middleware.params :as params])
(require '[ring.middleware.keyword-params :as keyword-params])
(require '[ring.middleware.json :as json])

;; Простой обработчик
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "<h1>Привет, мир!</h1>"})

;; Обработчик с параметрами
(defn hello [request]
  (let [name (get-in request [:params :name] "незнакомец")]
    {:status 200
     :headers {"Content-Type" "text/html; charset=utf-8"}
     :body (str "<h1>Привет, " name "!</h1>")}))

;; Применение промежуточного ПО (middleware)
(def app
  (-> hello
      keyword-params/wrap-keyword-params
      params/wrap-params
      json/wrap-json-response))

;; Запуск сервера
(defn start-server []
  (jetty/run-jetty app {:port 3000 :join? false}))

Compojure (маршрутизация)

(require '[compojure.core :refer :all])
(require '[compojure.route :as route])
(require '[ring.middleware.defaults :refer [wrap-defaults site-defaults]])

;; Определение маршрутов
(defroutes app-routes
  (GET "/" [] "<h1>Главная страница</h1>")
  (GET "/hello/:name" [name] (str "<h1>Привет, " name "!</h1>"))
  (POST "/api/data" request
    (let [body (:body request)]
      {:status 200
       :headers {"Content-Type" "application/json"}
       :body {:received body, :status "ok"}}))
  (route/resources "/")
  (route/not-found "<h1>Страница не найдена</h1>"))

;; Применение middleware
(def app
  (wrap-defaults app-routes site-defaults))

Hiccup (генерация HTML)

(require '[hiccup.core :refer [html]])
(require '[hiccup.page :refer [html5 include-css include-js]])

;; Создание HTML
(html [:div {:class "container"}
       [:h1 "Заголовок"]
       [:p "Абзац текста"]])

;; Создание полной HTML-страницы
(defn index-page [title content]
  (html5
    [:head
     [:title title]
     (include-css "/css/style.css")
     (include-js "/js/script.js")]
    [:body
     [:div {:class "container"}
      [:h1 title]
      content
      [:footer "© 2025 Мой сайт"]]]))

;; Использование в обработчике Ring
(defn home [request]
  {:status 200
   :headers {"Content-Type" "text/html; charset=utf-8"}
   :body (index-page "Главная страница" 
                    [:div
                     [:p "Добро пожаловать на мой сайт!"]
                     [:a {:href "/about"} "О нас"]])})

ClojureScript

ClojureScript — это компилятор Clojure в JavaScript, позволяющий писать фронтенд-код на Clojure.

;; project.clj для Leiningen
(defproject my-app "0.1.0"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [org.clojure/clojurescript "1.11.60"]]
  :plugins [[lein-cljsbuild "1.1.8"]]
  :cljsbuild {:builds [{:source-paths ["src"]
                        :compiler {:output-to "resources/public/js/main.js"
                                  :optimizations :advanced}}]})

;; Основной файл ClojureScript
(ns my-app.core
  (:require [goog.dom :as dom]
            [goog.events :as events]))

;; Доступ к DOM
(def app-element (dom/getElement "app"))

;; Изменение содержимого
(set! (.-innerHTML app-element) "<h1>Привет из ClojureScript!</h1>")

;; Обработка событий
(events/listen app-element "click"
               (fn [event]
                 (js/alert "Элемент был нажат!")))

;; Вызов JavaScript-функций
(js/console.log "Сообщение в консоль")

Reagent (React обертка)

(ns my-app.core
  (:require [reagent.core :as r]
            [reagent.dom :as rdom]))

;; Определение состояния
(def app-state (r/atom {:count 0}))

;; Компонент счетчика
(defn counter []
  (let [count (:count @app-state)]
    [:div
     [:h1 "Счетчик: " count]
     [:button {:on-click #(swap! app-state update :count inc)} "Увеличить"]
     [:button {:on-click #(swap! app-state update :count dec)} "Уменьшить"]]))

;; Рендеринг компонента
(defn ^:export init []
  (rdom/render [counter]
               (js/document.getElementById "app")))

Работа с файловой системой и I/O

(require '[clojure.java.io :as io])

;; Чтение файла
(slurp "file.txt")  ;; читает весь файл как строку

;; Построчное чтение
(with-open [rdr (io/reader "file.txt")]
  (doall (line-seq rdr)))

;; Запись в файл
(spit "output.txt" "Содержимое файла")

;; Добавление в файл
(spit "output.txt" "\nНовая строка" :append true)

;; Работа с бинарными файлами
(with-open [in (io/input-stream "input.bin")
            out (io/output-stream "output.bin")]
  (io/copy in out))

;; Работа с директориями
(def files (file-seq (io/file "src")))

;; Создание директорий
(.mkdir (io/file "new-dir"))
(.mkdirs (io/file "path/to/new-dir"))

;; Проверка существования файла
(.exists (io/file "file.txt"))

;; Удаление файла
(.delete (io/file "file.txt"))

Сериализация данных

EDN (Extensible Data Notation)

(require '[clojure.edn :as edn])

;; Чтение EDN из строки
(edn/read-string "{:name \"Иван\" :age 30}")  ;; => {:name "Иван" :age 30}

;; Запись данных в EDN
(pr-str {:name "Иван" :age 30})  ;; => "{:name \"Иван\", :age 30}"

;; Чтение EDN из файла
(edn/read-string (slurp "data.edn"))

;; Запись данных в EDN-файл
(spit "data.edn" (pr-str {:name "Иван" :age 30}))

JSON

(require '[cheshire.core :as json])

;; Преобразование Clojure в JSON
(json/generate-string {:name "Иван" :age 30})
;; => "{\"name\":\"Иван\",\"age\":30}"

;; Преобразование JSON в Clojure
(json/parse-string "{\"name\":\"Иван\",\"age\":30}")
;; => {"name" "Иван", "age" 30}

;; С ключами в виде ключевых слов
(json/parse-string "{\"name\":\"Иван\",\"age\":30}" true)
;; => {:name "Иван", :age 30}

;; Запись в файл
(spit "data.json" (json/generate-string {:name "Иван" :age 30} {:pretty true}))

;; Чтение из файла
(json/parse-string (slurp "data.json") true)

Transit

(require '[cognitect.transit :as transit])
(import '[java.io ByteArrayInputStream ByteArrayOutputStream])

;; Запись в Transit (JSON формат)
(defn write-transit [data]
  (let [out (ByteArrayOutputStream.)
        writer (transit/writer out :json)]
    (transit/write writer data)
    (.toString out)))

;; Чтение из Transit
(defn read-transit [transit-str]
  (let [in (ByteArrayInputStream. (.getBytes transit-str))
        reader (transit/reader in :json)]
    (transit/read reader)))

;; Пример использования
(def data {:name "Иван" :age 30 :skills ["clojure" "java"]})
(def transit-str (write-transit data))
(def result (read-transit transit-str))

Системы управления жизненным циклом компонентов

Component

(require '[com.stuartsierra.component :as component])

;; Определение компонента базы данных
(defrecord Database [host port connection]
  component/Lifecycle
  
  (start [this]
    (println "Подключение к базе данных" host ":" port)
    (let [conn (str "Connected to " host ":" port)]
      (assoc this :connection conn)))
  
  (stop [this]
    (println "Отключение от базы данных")
    (assoc this :connection nil)))

;; Определение компонента веб-сервера
(defrecord WebServer [port database server]
  component/Lifecycle
  
  (start [this]
    (println "Запуск веб-сервера на порту" port)
    (println "Использует соединение:" (:connection database))
    (assoc this :server (str "Server running on port " port)))
  
  (stop [this]
    (println "Остановка веб-сервера")
    (assoc this :server nil)))

;; Создание системы
(defn new-system [config]
  (let [{:keys [db-host db-port http-port]} config]
    (component/system-map
      :database (map->Database {:host db-host :port db-port})
      :web-server (component/using
                    (map->WebServer {:port http-port})
                    [:database]))))

;; Использование системы
(def system (new-system {:db-host "localhost" :db-port 5432 :http-port 8080}))
(def running-system (component/start system))
;; ...
(component/stop running-system)

Mount

(require '[mount.core :as mount])

;; Определение состояний
(mount/defstate db-connection
  :start (do
           (println "Подключение к базе данных")
           {:connection "db-connection"})
  :stop (do
          (println "Закрытие соединения с базой данных")
          nil))

(mount/defstate web-server
  :start (do
           (println "Запуск веб-сервера с" (:connection db-connection))
           {:server "web-server"})
  :stop (do
          (println "Остановка веб-сервера")
          nil))

;; Запуск и остановка состояний
(mount/start)  ;; запускает все состояния
(mount/stop)   ;; останавливает все состояния

;; Запуск только определенных состояний
(mount/start #'db-connection)

;; Перезапуск состояний
(mount/stop)
(mount/start)

Оптимизация производительности

;; Использование типизированных хинтов
(defn ^double add-doubles [^double x ^double y]
  (+ x y))

;; Избегание рефлексии
(set! *warn-on-reflection* true)

(defn string-length [s]
  (.length s))  ;; выдаст предупреждение о рефлексии

(defn string-length [^String s]
  (.length s))  ;; без рефлексии

;; Использование примитивных массивов
(def ^"[D" double-array (double-array [1.0 2.0 3.0]))
(aget double-array 1)  ;; => 2.0

;; Профилирование с помощью criterium
(require '[criterium.core :as c])

(c/bench (reduce + (range 1000)))
;; Выводит подробную статистику производительности

;; Использование transient коллекций для промежуточных операций
(defn efficient-map [f coll]
  (persistent!
    (reduce (fn [acc item]
              (conj! acc (f item)))
            (transient [])
            coll)))

;; Предварительное выделение размера для векторов
(def large-vector (vec (range 1000000)))

;; Использование reduce вместо map + filter для сложных преобразований
(defn process-data [data]
  (reduce (fn [result item]
            (if (even? item)
              (conj result (* item item))
              result))
          []
          data))

Функциональные паттерны

Монады

;; Maybe монада
(defn maybe-bind [m f]
  (when (some? m)
    (f m)))

(defn maybe-return [x]
  x)

;; Использование Maybe монады
(-> 5
    maybe-return
    (maybe-bind #(maybe-return (inc %)))
    (maybe-bind #(maybe-return (* % 2))))  ;; => 12

(-> nil
    maybe-return
    (maybe-bind #(maybe-return (inc %)))
    (maybe-bind #(maybe-return (* % 2))))  ;; => nil

;; Either монада (с использованием записей)
(defrecord Left [value])
(defrecord Right [value])

(defn either-bind [e f]
  (if (instance? Left e)
    e
    (f (:value e))))

(defn either-return [x]
  (Right. x))

;; Использование Either монады
(defn safe-div [x y]
  (if (zero? y)
    (Left. "Деление на ноль")
    (Right. (/ x y))))

(-> (either-return 10)
    (either-bind #(safe-div % 2))
    (either-bind #(safe-div % 0)))  ;; => #user.Left{:value "Деление на ноль"}

Функторы и аппликативные функторы

;; Функтор для векторов
(defn fmap [f v]
  (mapv f v))

(fmap inc [1 2 3])  ;; => [2 3 4]

;; Аппликативный функтор для векторов
(defn ap [fs xs]
  (for [f fs
        x xs]
    (f x)))

(ap [inc dec] [1 2 3])  ;; => (2 3 4 0 1 2)

;; Комбинация функторов
(-> [1 2 3]
    (fmap inc)
    (fmap #(* % 2)))  ;; => [4 6 8]

Инструменты разработки

REPL-driven development

;; Загрузка файла в REPL
(load-file "src/my_app/core.clj")

;; Перезагрузка пространства имён
(require '[my-app.core :as core] :reload)

;; Перезагрузка всех зависимостей
(require '[my-app.core :as core] :reload-all)

;; Инструментирование функции для отладки
(defn debug-fn [f]
  (fn [& args]
    (println "Вызов с аргументами:" args)
    (let [result (apply f args)]
      (println "Результат:" result)
      result)))

(def debug-add (debug-fn +))
(debug-add 1 2 3)  ;; Выводит отладочную информацию

Инструменты сборки

Leiningen

;; project.clj
(defproject my-app "0.1.0-SNAPSHOT"
  :description "My Clojure application"
  :url "http://example.com/my-app"
  :license {:name "EPL-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [compojure "1.7.0"]
                 [ring "1.9.6"]]
  :main ^:skip-aot my-app.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
             :dev {:dependencies [[midje "1.10.9"]]
                   :plugins [[lein-midje "3.2.2"]]}})

deps.edn

;; deps.edn
{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        compojure/compojure {:mvn/version "1.7.0"}
        ring/ring {:mvn/version "1.9.6"}}
 :aliases
 {:test {:extra-paths ["test"]
         :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
  :run {:main-opts ["-m" "my-app.core"]}
  :uberjar {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}}
            :exec-fn hf.depstar/uberjar
            :exec-args {:aot true
                       :jar "target/my-app.jar"
                       :main-class "my-app.core"}}}}

Отладка и профилирование

;; Отладка с помощью clojure.tools.trace
(require '[clojure.tools.trace :as t])

(t/trace-ns 'my-app.core)  ;; трассировка всех функций в пространстве имён
(t/trace-vars my-app.core/my-function)  ;; трассировка конкретной функции

;; Профилирование с помощью YourKit
;; Запуск JVM с опциями:
;; -agentpath:/path/to/libyjpagent.so

;; Профилирование с помощью VisualVM
;; Запуск JVM с опциями:
;; -Dcom.sun.management.jmxremote

Библиотеки для работы с данными

data.csv

(require '[clojure.data.csv :as csv])
(require '[clojure.java.io :as io])

;; Чтение CSV
(with-open [reader (io/reader "data.csv")]
  (doall
    (csv/read-csv reader)))

;; Запись CSV
(with-open [writer (io/writer "output.csv")]
  (csv/write-csv writer
                [["Name" "Age" "City"]
                 ["Иван" "30" "Москва"]
                 ["Мария" "25" "Санкт-Петербург"]]))

cheshire (JSON)

(require '[cheshire.core :as json])

;; Чтение JSON из строки
(json/parse-string "{\"name\":\"Иван\",\"age\":30}" true)  ;; => {:name "Иван", :age 30}

;; Запись объекта в JSON
(json/generate-string {:name "Иван" :age 30})  ;; => "{\"name\":\"Иван\",\"age\":30}"

;; Форматированный JSON
(json/generate-string {:name "Иван" :age 30} {:pretty true})

;; Чтение JSON из файла
(json/parse-stream (io/reader "data.json") true)

;; Запись JSON в файл
(with-open [writer (io/writer "output.json")]
  (json/generate-stream {:name "Иван" :age 30} writer {:pretty true}))

clj-time/java.time

;; clj-time (обертка над Joda-Time)
(require '[clj-time.core :as t])
(require '[clj-time.format :as f])

(def now (t/now))  ;; текущая дата и время
(t/plus now (t/days 1))  ;; добавление дня
(t/minus now (t/hours 2))  ;; вычитание часов

;; Форматирование даты
(def custom-formatter (f/formatter "dd.MM.yyyy HH:mm"))
(f/unparse custom-formatter now)  ;; => "21.05.2025 11:21"
(f/parse custom-formatter "21.05.2025 11:21")

;; java.time (Java 8+)
(import '[java.time LocalDate LocalDateTime ZonedDateTime])
(import '[java.time.format DateTimeFormatter])

(def today (LocalDate/now))
(def tomorrow (.plusDays today 1))

(def formatter (DateTimeFormatter/ofPattern "dd.MM.yyyy"))
(.format today formatter)  ;; => "21.05.2025"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment