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))
Ленивые последовательности вычисляются только при необходимости, что позволяет эффективно работать с большими или бесконечными коллекциями.
;; Создание ленивой последовательности
(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
Трансдьюсеры — это преобразователи, которые можно компонировать и применять к различным контекстам обработки данных.
;; Определение трансдьюсеров
(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]
Спецификации позволяют описывать и валидировать структуру данных.
(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)
(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 предоставляет 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)
(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 "Иван"}))
(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})
(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}))
(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))
(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 — это компилятор 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 "Сообщение в консоль")
(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")))
(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"))
(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}))
(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)
(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))
(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)
(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
(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) ;; Выводит отладочную информацию
;; 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
{: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
(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" "Санкт-Петербург"]]))
(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 (обертка над 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"