- Шпаргалка по языку Clojure
- Пространства имен
- Базовый синтаксис
- Управляющие конструкции
- Функции в Clojure
- Деструктуризация в Clojure
- Работа с коллекциями
- Многопоточность и параллелизм
- Макросы
- Взаимодействие с Java
- Ключевые слова в Clojure
- Работа с исключениями в Clojure
- Работа с JDBC в Clojure
- Работа с JPA в Clojure
- Создание HTTP сервера в Clojure
- Работа с графической библиотекой Swing
- DSL на Clojure
Clojure - это современный диалект языка Lisp, разработанный для Java Virtual Machine (JVM).
Пространства имен в Clojure - это механизм для организации и группировки кода. Они похожи на модули или пакеты в других языках программирования. Вот ключевые аспекты пространств имен в Clojure:
- Каждый файл Clojure обычно представляет собой отдельное пространство имен.
- Пространство имен определяется в начале файла с помощью формы
ns
:
(ns my-project.core)
- Имя пространства имен обычно соответствует пути к файлу в структуре проекта.
- Для импорта функций из других пространств имен используется
require
:
(ns my-namespace
(:require [other-namespace :as other]))
- Можно импортировать отдельные функции:
(ns my-namespace
(:require [other-namespace :refer [specific-function]]))
- Или импортировать все функции:
(ns my-namespace
(:require [other-namespace :refer :all]))
- Пространства имен можно переименовывать для удобства:
(ns my-namespace
(:require [very-long-namespace-name :as short-name]))
- Функции, определенные с
defn-
, являются приватными и доступны только внутри своего пространства имен.
- Пространства имен помогают логически группировать связанные функции и данные.
- Они предотвращают конфликты имен между различными частями программы.
Пространства имен в Clojure обеспечивают модульность и помогают организовать код в более управляемые и понятные структуры.
- Числа:
42
(целое),-1.5
(с плавающей точкой),22/7
(рациональное) - Строки:
"hello"
- Символы:
\a
,\newline
- Ключевые слова:
:keyword
,:my.namespace/keyword
- Символы:
my-symbol
,my.namespace/symbol
- Булевы значения:
true
,false
- Null-значение:
nil
- Векторы:
[1 2 3]
- Списки:
'(1 2 3)
или(list 1 2 3)
- Множества:
#{1 2 3}
- Ассоциативные массивы:
{:key1 "value1", :key2 "value2"}
В Clojure существует несколько основных управляющих конструкций:
if Простейшая условная конструкция:
(if condition
then-expression
else-expression)
Вот несколько примеров использования if
в Clojure:
- Простое условное выражение:
(if (> 5 3)
"5 больше 3"
"5 не больше 3")
; Результат: "5 больше 3"
- Использование с функциями:
(defn check-age [age]
(if (>= age 18)
"Совершеннолетний"
"Несовершеннолетний"))
(check-age 20) ; Результат: "Совершеннолетний"
(check-age 15) ; Результат: "Несовершеннолетний"
- Вложенные
if
:
(defn grade [score]
(if (>= score 90)
"A"
(if (>= score 80)
"B"
(if (>= score 70)
"C"
"F"))))
(grade 85) ; Результат: "B"
- Использование с
let
:
(let [x 10]
(if (even? x)
(str x " четное")
(str x " нечетное")))
; Результат: "10 четное"
if
без else:
(if true
(println "Это выполнится"))
; Выводит: Это выполнится
; Результат: nil
if
в Clojure является выражением и всегда возвращает значение. Если ветка else
не указана и условие ложно, if
возвращает nil
.
when Выполняет блок кода, если условие истинно:
(when condition
expression1
expression2)
when
в Clojure используется для условного выполнения блока кода, когда нужно обработать только истинный случай. Вот пример использования when
:
(defn greet-user [name]
(when (not-empty name)
(println "Hello," name)
(println "Welcome to our application!")))
(greet-user "Alice")
В этом примере:
- Функция
greet-user
принимает аргументname
. when
проверяет, чтоname
не пустой, используя функциюnot-empty
.- Если условие истинно, выполняются оба выражения внутри
when
:- Печатается приветствие с именем пользователя
- Печатается дополнительное сообщение
Если name
пустой, ничего не происходит, и функция неявно возвращает nil
.
when
особенно полезен, когда нужно выполнить несколько выражений при истинном условии, но не требуется обрабатывать ложный случай. В отличие от if
, when
имеет неявный do
и не требует ветки else
.
cond Множественное ветвление:
(cond
condition1 result1
condition2 result2
:else default-result)
cond
в Clojure используется для множественного ветвления, когда нужно проверить несколько условий последовательно. Вот пример использования cond
:
(defn grade-student [score]
(cond
(>= score 90) "A"
(>= score 80) "B"
(>= score 70) "C"
(>= score 60) "D"
:else "F"))
(println (grade-student 85)) ; Выведет "B"
(println (grade-student 92)) ; Выведет "A"
(println (grade-student 45)) ; Выведет "F"
В этом примере:
- Функция
grade-student
принимает числовой аргументscore
. cond
последовательно проверяет условия:- Если
score
>= 90, возвращается "A" - Если
score
>= 80, возвращается "B" - И так далее
- Если
- Последнее условие
:else
служит как "catch-all" и выполняется, если ни одно из предыдущих условий не было истинным.
cond
оценивает пары условие-результат сверху вниз и возвращает результат первого истинного условия. Если ни одно условие не истинно и нет :else
, cond
возвращает nil
.
Этот макрос особенно полезен, когда нужно обработать множество различных случаев, и использование вложенных if
сделало бы код менее читаемым.
doseq Выполняет тело для каждого элемента последовательности:
(doseq [item collection]
(println item))
doseq
в Clojure используется для итерации по последовательности и выполнения побочных эффектов. Вот пример использования doseq
:
(doseq [n [0 1 2]]
(println n))
Этот код выведет:
0
1
2
В данном примере:
doseq
итерирует по вектору[0 1 2]
- На каждой итерации значение присваивается переменной
n
- Тело
doseq
выполняетprintln
для каждого значенияn
doseq
особенно полезен, когда нужно выполнить действие для каждого элемента последовательности, не создавая новую последовательность результатов. Это делает его подходящим для операций с побочными эффектами, таких как вывод на экран или запись в файл.
Важно отметить, что doseq
, в отличие от for
, не возвращает значение. Он используется именно для выполнения побочных эффектов, а не для создания новых последовательностей.
dotimes Выполняет тело заданное количество раз:
(dotimes [i 5]
(println i))
dotimes
в Clojure используется для выполнения блока кода заданное количество раз. Вот пример использования dotimes
:
(dotimes [i 5]
(println "Iteration" i))
Этот код выведет:
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
В этом примере:
dotimes
создает локальную переменнуюi
, которая принимает значения от 0 до 4 (всего 5 итераций).- Тело
dotimes
выполняется для каждого значенияi
. - На каждой итерации выводится строка "Iteration" и текущее значение
i
.
dotimes
полезен, когда нужно выполнить действие фиксированное количество раз. Он часто используется для простых циклов, где важен сам факт повторения, а не конкретные значения счетчика.
Важно помнить, что dotimes
, как и doseq
, используется в основном для выполнения побочных эффектов и не возвращает значение (точнее, всегда возвращает nil
).
do Выполняет несколько выражений последовательно:
(do
expression1
expression2
expression3)
do
в Clojure используется для группировки нескольких выражений в один блок. Это особенно полезно, когда нужно выполнить несколько действий там, где ожидается одно выражение. Вот пример использования do
:
(defn perform-actions [x]
(do
(println "Starting actions")
(println "Processing value:" x)
(let [result (* x 2)]
(println "Result is:" result)
result)))
(perform-actions 5)
Этот код выведет:
Starting actions
Processing value: 5
Result is: 10
10
В этом примере:
- Функция
perform-actions
используетdo
для группировки нескольких выражений. - Внутри
do
выполняются три действия: два вывода на экран и вычисление результата. - Последнее выражение в
do
(в данном случаеresult
) становится возвращаемым значением всего блока.
do
часто используется в следующих ситуациях:
- Внутри функций, где нужно выполнить несколько действий.
- В условных выражениях, где требуется выполнить несколько действий в одной ветви.
- Для группировки побочных эффектов вместе с вычислением результата.
Важно отметить, что многие формы в Clojure (например, let
, defn
, when
) имеют неявный do
, поэтому в них не требуется явно использовать do
для группировки выражений.
Эти конструкции позволяют управлять потоком выполнения программы в Clojure, обеспечивая гибкость и выразительность кода.
В Clojure существует несколько способов определения функций:
1. Использование defn
Это наиболее распространенный способ определения именованных функций:
(defn hello [name]
(str "Hello " name))
2. Анонимные функции с fn
Для создания безымянных функций используется специальная форма fn:
(fn [x y] (+ x y))
3. Сокращенный синтаксис анонимных функций
Clojure предоставляет краткую форму записи анонимных функций:
#(+ %1 %2)
Мультиметодные функции
Clojure позволяет определять функции с разным количеством аргументов:
(defn hello
([] "Hello World")
([name] (str "Hello " name)))
Функции с переменным числом аргументов
Можно создавать функции, принимающие произвольное количество аргументов:
(defn sum [& numbers]
(apply + numbers))
Деструктуризация в аргументах
При определении функции можно использовать деструктуризацию:
(defn sum [[x y]] (+ x y))
Эти различные способы определения функций делают Clojure гибким и выразительным языком, позволяя выбрать наиболее подходящий синтаксис для конкретной задачи.
Функции ->
(thread-first) и ->>
(thread-last) в Clojure используются для объединения операций и улучшения читаемости кода. Они позволяют выразить последовательность операций над данными в более линейной форме.
- Вставляет каждое выражение как первый аргумент следующей формы.
- Подходит для функций, которые принимают основной аргумент первым.
- Часто используется с функциями, работающими со структурами данных, такими как
assoc
,update
,dissoc
,get
.
Пример:
(-> person
:hair-color
name
clojure.string/upper-case)
- Вставляет каждое выражение как последний аргумент следующей формы.
- Идеально подходит для работы с последовательностями и функциями, принимающими коллекцию последним аргументом.
- Часто используется с функциями
map
,filter
,reduce
,into
.
Пример:
(->> (range 10)
(filter odd?)
(map #(* % %))
(reduce +))
Выбор макроса зависит от сигнатуры используемых функций:
- Используйте
->
для навигации по вложенным структурам и работы с отдельными значениями. - Применяйте
->>
для операций с последовательностями и коллекциями.
- Улучшает читаемость кода, особенно при работе с длинными цепочками преобразований.
- Уменьшает вложенность выражений.
- Позволяет легко добавлять или удалять шаги в цепочке операций.
Важно помнить, что не всегда необходимо использовать эти макросы. Их следует применять, когда они действительно улучшают понятность и выразительность кода.
В Clojure существует несколько способов определения функций с использованием деструктуризации:
1. Деструктуризация векторов и списков:
(defn print-coordinates [[x y]]
(println "X:" x "Y:" y))
(print-coordinates [10 20])
2. Деструктуризация карт:
(defn greet [{:keys [name location]}]
(println "Hey there" name ", how's the weather in" location "?"))
(greet {:name "Josie" :location "San Francisco"})
3. Деструктуризация с значениями по умолчанию:
(defn greet [{:keys [name location] :or {location "Unknown"}}]
(println "Hello" name "from" location))
(greet {:name "John"})
4. Использование let для деструктуризации:
(defn area [triangle]
(let [{b :base h :height} triangle]
(* b h 0.5)))
(area {:base 5 :height 10})
5. Деструктуризация с использованием :as:
(defn process-numbers [numbers]
(let [[first second & rest :as all] numbers]
(println "First:" first)
(println "Second:" second)
(println "Rest:" rest)
(println "All:" all)))
(process-numbers [1 2 3 4 5])
6. Деструктуризация вложенных структур:
(defn process-user [{{:keys [street city]} :address :keys [name]}]
(println name "lives in" city "on" street))
(process-user {:name "Alice" :address {:street "123 Main St" :city "Wonderland"}})
Эти методы деструктуризации позволяют создавать более чистый и выразительный код, упрощая работу со сложными структурами данных в аргументах и теле функций.
Основные функции:
count
: подсчет элементовconj
: добавление элементаfirst
,second
,last
: доступ к элементамmap
,filter
,reduce
: функции высшего порядка
- Вектор:
[1 2 3]
или(vector 1 2 3)
- Список:
'(1 2 3)
или(list 1 2 3)
- Множество:
#{1 2 3}
- Ассоциативный массив:
{:key1 "value1", :key2 "value2"}
count
: подсчет элементовconj
: добавление элементаfirst
: получение первого элементаrest
: получение всех элементов кроме первогоnth
: получение элемента по индексуget
: получение элемента по ключу или индексу
Функция count
в Clojure используется для подсчета количества элементов в коллекции. Вот несколько примеров использования count
:
- Подсчет элементов в векторе:
(count [1 2 3 4 5])
; Результат: 5
- Подсчет символов в строке:
(count "Hello, World!")
; Результат: 13
- Подсчет элементов в списке:
(count '(a b c d))
; Результат: 4
- Подсчет элементов в множестве:
(count #{:a :b :c})
; Результат: 3
- Подсчет пар ключ-значение в ассоциативном массиве:
(count {:a 1 :b 2 :c 3})
; Результат: 3
- Использование с пустыми коллекциями:
(count [])
; Результат: 0
(count "")
; Результат: 0
- Подсчет элементов в последовательности:
(count (range 10))
; Результат: 10
Функция count
работает эффективно для большинства типов коллекций в Clojure, предоставляя быстрый способ определения размера коллекции.
Функция conj
в Clojure используется для добавления одного или нескольких элементов к коллекции. Важно отметить, что conj
добавляет элементы наиболее эффективным способом для конкретного типа коллекции. Вот несколько примеров использования conj
:
- Добавление элемента в вектор (в конец):
(conj [1 2 3] 4)
; Результат: [1 2 3 4]
- Добавление элемента в список (в начало):
(conj '(2 3 4) 1)
; Результат: (1 2 3 4)
- Добавление элемента в множество:
(conj #{1 2 3} 4)
; Результат: #{1 2 3 4}
- Добавление пары ключ-значение в ассоциативный массив:
(conj {:a 1 :b 2} [:c 3])
; Результат: {:a 1, :b 2, :c 3}
- Добавление нескольких элементов:
(conj [1 2] 3 4 5)
; Результат: [1 2 3 4 5]
- Добавление элементов к пустой коллекции:
(conj [] 1 2 3)
; Результат: [1 2 3]
conj
создает новую коллекцию, не изменяя исходную, что соответствует принципам неизменяемости в Clojure.
Функция first
в Clojure используется для получения первого элемента последовательности или коллекции. Вот несколько примеров использования first
:
- Получение первого элемента вектора:
(first [1 2 3 4 5])
; Результат: 1
- Получение первого элемента списка:
(first '(a b c d))
; Результат: a
- Получение первого символа строки:
(first "Hello")
; Результат: \H
- Работа с пустыми коллекциями:
(first [])
; Результат: nil
- Получение первой пары ключ-значение из ассоциативного массива:
(first {:a 1 :b 2 :c 3})
; Результат: [:a 1]
- Использование с бесконечными последовательностями:
(first (range))
; Результат: 0
- Комбинирование с другими функциями:
(first (filter odd? [2 4 6 7 8 9]))
; Результат: 7
Функция first
часто используется в сочетании с другими функциями для обработки последовательностей и коллекций в функциональном стиле программирования.
Функция rest
в Clojure используется для получения последовательности, содержащей все элементы исходной последовательности, кроме первого. Вот несколько примеров использования rest
:
- Получение остатка вектора:
(rest [1 2 3 4 5])
; Результат: (2 3 4 5)
- Получение остатка списка:
(rest '(a b c d))
; Результат: (b c d)
- Получение остатка строки:
(rest "Hello")
; Результат: (\e \l \l \o)
- Работа с пустыми коллекциями:
(rest [])
; Результат: ()
- Использование с бесконечными последовательностями:
(take 5 (rest (range)))
; Результат: (1 2 3 4 5)
- Комбинирование с другими функциями:
(rest (filter odd? [1 3 5 7 9]))
; Результат: (3 5 7 9)
Важно отметить, что rest
всегда возвращает последовательность, даже если исходная коллекция пуста. Это отличает ее от функции next
, которая возвращает nil
для пустых коллекций.
Функция nth
в Clojure используется для получения элемента последовательности по его индексу. Вот несколько примеров использования nth
:
- Получение элемента вектора по индексу:
(nth [10 20 30 40 50] 2)
; Результат: 30
- Получение элемента списка:
(nth '(a b c d e) 3)
; Результат: d
- Получение символа из строки:
(nth "Hello" 1)
; Результат: \e
- Использование с необязательным значением по умолчанию:
(nth [1 2 3] 5 :not-found)
; Результат: :not-found
- Работа с бесконечными последовательностями:
(nth (iterate inc 1) 10)
; Результат: 11
- Использование отрицательных индексов (вызовет исключение):
(nth [1 2 3] -1)
; Вызовет IndexOutOfBoundsException
nth
эффективно работает с индексированными коллекциями (векторы, строки), но может быть менее эффективным для последовательностей, требующих линейного прохода (например, списки).
Функция get
в Clojure используется для получения значения по ключу из ассоциативных структур данных или элемента по индексу из индексированных коллекций. Вот несколько примеров использования get
:
- Получение значения из ассоциативного массива:
(get {:a 1 :b 2 :c 3} :b)
; Результат: 2
- Получение элемента вектора по индексу:
(get [10 20 30 40 50] 2)
; Результат: 30
- Использование значения по умолчанию:
(get {:a 1 :b 2} :c "not found")
; Результат: "not found"
- Работа с вложенными структурами:
(get-in {:user {:name "John" :age 30}} [:user :name])
; Результат: "John"
- Использование с множествами:
(get #{:a :b :c} :b)
; Результат: :b
- Получение значения из строки по индексу:
(get "Hello" 1)
; Результат: \e
get
безопасно работает с nil
и возвращает nil
, если ключ не найден (если не указано значение по умолчанию).
map
: применение функции к каждому элементуfilter
: фильтрация элементов по предикатуremove
: удаление элементов по предикатуreduce
: свертка коллекцииinto
: добавление элементов одной коллекции в другую
Функция map
в Clojure применяет заданную функцию к каждому элементу последовательности (или нескольких последовательностей) и возвращает новую последовательность результатов. Вот несколько примеров использования map
:
- Применение функции к одной последовательности:
(map inc [1 2 3 4 5])
; Результат: (2 3 4 5 6)
- Использование анонимной функции:
(map #(* % %) [1 2 3 4 5])
; Результат: (1 4 9 16 25)
- Применение функции к нескольким последовательностям:
(map + [1 2 3] [4 5 6])
; Результат: (5 7 9)
- Работа со строками:
(map clojure.string/upper-case ["a" "b" "c"])
; Результат: ("A" "B" "C")
- Использование с функцией, принимающей несколько аргументов:
(map #(format "%s: %d" %1 %2) ["A" "B" "C"] [1 2 3])
; Результат: ("A: 1" "B: 2" "C: 3")
- Комбинирование с другими функциями:
(->> [1 2 3 4 5]
(map #(* % 2))
(filter even?))
; Результат: (2 4 6 8 10)
map
- это мощный инструмент для преобразования данных, особенно в сочетании с другими функциями высшего порядка в Clojure.
Функция filter
в Clojure используется для создания новой последовательности, содержащей только те элементы исходной последовательности, которые удовлетворяют заданному предикату. Вот несколько примеров использования filter
:
- Фильтрация четных чисел:
(filter even? [1 2 3 4 5 6])
; Результат: (2 4 6)
- Использование анонимной функции:
(filter #(> % 3) [1 2 3 4 5 6])
; Результат: (4 5 6)
- Фильтрация строк по длине:
(filter #(> (count %) 3) ["a" "ab" "abc" "abcd" "abcde"])
; Результат: ("abcd" "abcde")
- Фильтрация ключей в ассоциативном массиве:
(filter (fn [[k v]] (keyword? k))
{:a 1 :b 2 "c" 3 "d" 4})
; Результат: ([:a 1] [:b 2])
- Комбинирование с другими функциями:
(->> [1 2 3 4 5 6 7 8 9 10]
(filter odd?)
(map #(* % %)))
; Результат: (1 9 25 49 81)
- Фильтрация с использованием предиката на основе индекса:
(filter-indexed #(odd? %1) ["a" "b" "c" "d" "e"])
; Результат: ("b" "d")
filter
часто используется в сочетании с другими функциями высшего порядка для создания сложных преобразований данных в функциональном стиле.
Функция remove
в Clojure используется для создания новой последовательности, исключая элементы, удовлетворяющие заданному предикату. По сути, это противоположность функции filter
. Вот несколько примеров использования remove
:
- Удаление четных чисел:
(remove even? [1 2 3 4 5 6])
; Результат: (1 3 5)
- Использование анонимной функции:
(remove #(< % 3) [1 2 3 4 5 6])
; Результат: (3 4 5 6)
- Удаление пустых строк:
(remove empty? ["" "a" "" "b" "c" ""])
; Результат: ("a" "b" "c")
- Удаление элементов из вектора по условию:
(remove #(= (count %) 3) ["a" "ab" "abc" "abcd"])
; Результат: ("a" "ab" "abcd")
- Комбинирование с другими функциями:
(->> (range 1 11)
(remove even?)
(map #(* % %)))
; Результат: (1 9 25 49 81)
- Удаление ключей из ассоциативного массива:
(into {} (remove (fn [[k v]] (keyword? k))
{:a 1 :b 2 "c" 3 "d" 4}))
; Результат: {"c" 3, "d" 4}
remove
часто используется в функциональном программировании для очистки данных или для создания новых коллекций, исключающих определенные элементы.
Функция reduce
в Clojure используется для свертки последовательности в одно значение путем последовательного применения функции к элементам. Вот несколько примеров использования reduce
:
- Суммирование чисел:
(reduce + [1 2 3 4 5])
; Результат: 15
- Нахождение максимального значения:
(reduce max [3 7 2 9 1 5])
; Результат: 9
- Конкатенация строк:
(reduce str ["Hello" " " "World" "!"])
; Результат: "Hello World!"
- Использование начального значения:
(reduce + 10 [1 2 3 4 5])
; Результат: 25 (10 + 1 + 2 + 3 + 4 + 5)
- Создание ассоциативного массива:
(reduce (fn [acc [k v]] (assoc acc k (* v v)))
{}
{:a 1 :b 2 :c 3})
; Результат: {:a 1, :b 4, :c 9}
- Подсчет частоты элементов:
(reduce (fn [acc x] (update acc x (fnil inc 0)))
{}
[1 2 3 2 1 3 3 4])
; Результат: {1 2, 2 2, 3 3, 4 1}
reduce
- мощный инструмент для обработки коллекций, особенно когда нужно агрегировать данные или преобразовать последовательность в одно значение.
Функция into
в Clojure используется для добавления всех элементов из одной коллекции в другую. Вот несколько примеров использования into
:
- Объединение двух векторов:
(into [1 2 3] [4 5 6])
; Результат: [1 2 3 4 5 6]
- Добавление элементов в множество:
(into #{:a :b} [:b :c :d])
; Результат: #{:a :b :c :d}
- Преобразование последовательности в map:
(into {} [[:a 1] [:b 2] [:c 3]])
; Результат: {:a 1, :b 2, :c 3}
- Объединение двух map'ов:
(into {:a 1 :b 2} {:c 3 :d 4})
; Результат: {:a 1, :b 2, :c 3, :d 4}
- Преобразование строки в вектор символов:
(into [] "hello")
; Результат: [\h \e \l \l \o]
Функция into
особенно полезна, когда нужно объединить коллекции разных типов или преобразовать один тип коллекции в другой.
take
: взять первые n элементовdrop
: отбросить первые n элементовtake-while
: брать элементы, пока выполняется условиеdrop-while
: отбрасывать элементы, пока выполняется условиеpartition
: разбиение на подпоследовательности
Функция take
в Clojure используется для получения первых n элементов из последовательности. Вот несколько примеров использования take
:
- Получение первых трех элементов из вектора:
(take 3 [1 2 3 4 5])
; Результат: (1 2 3)
- Работа с бесконечными последовательностями:
(take 5 (range))
; Результат: (0 1 2 3 4)
- Получение элементов из строки:
(take 4 "Hello, World!")
; Результат: (\H \e \l \l)
- Использование с пустой коллекцией:
(take 3 [])
; Результат: ()
- Получение всех элементов, если n больше длины коллекции:
(take 10 [1 2 3])
; Результат: (1 2 3)
- Комбинирование с другими функциями:
(take 3 (filter even? (range)))
; Результат: (0 2 4)
take
часто используется для работы с большими или бесконечными последовательностями, когда нужно ограничить количество обрабатываемых элементов.
Функция drop
в Clojure используется для создания последовательности, отбрасывая первые n элементов из исходной последовательности. Вот несколько примеров использования drop
:
- Отбрасывание первых трех элементов вектора:
(drop 3 [1 2 3 4 5 6])
; Результат: (4 5 6)
- Работа с бесконечными последовательностями:
(take 5 (drop 10 (range)))
; Результат: (10 11 12 13 14)
- Отбрасывание символов из строки:
(apply str (drop 7 "Hello, World!"))
; Результат: "World!"
- Использование с пустой коллекцией:
(drop 3 [])
; Результат: ()
- Отбрасывание всех элементов, если n больше длины коллекции:
(drop 10 [1 2 3 4 5])
; Результат: ()
- Комбинирование с другими функциями:
(drop-while even? [2 4 6 7 8 9])
; Результат: (7 8 9)
drop
часто используется в сочетании с другими функциями для обработки последовательностей, особенно когда нужно пропустить определенное количество элементов в начале последовательности.
Функция take-while
в Clojure используется для создания последовательности, содержащей элементы из начала исходной последовательности, пока предикат возвращает true. Как только предикат возвращает false для какого-либо элемента, take-while
прекращает работу. Вот несколько примеров использования take-while
:
- Получение чисел меньше 5:
(take-while #(< % 5) [1 2 3 4 5 6 7])
; Результат: (1 2 3 4)
- Получение положительных чисел:
(take-while pos? [3 2 1 0 -1 -2 3 4])
; Результат: (3 2 1)
- Работа со строками:
(take-while #(not= % \space) "Hello World")
; Результат: (\H \e \l \l \o)
- Использование с бесконечными последовательностями:
(take-while #(< % 1000) (map #(* % %) (range)))
; Результат: (0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 529 576 625 676 729 784 841 900 961)
- Комбинирование с другими функциями:
(->> (range)
(map #(* % 3))
(take-while #(< % 20)))
; Результат: (0 3 6 9 12 15 18)
take-while
особенно полезна, когда нужно получить элементы последовательности, удовлетворяющие определенному условию, до первого элемента, не удовлетворяющего этому условию.
Функция drop-while
в Clojure используется для создания последовательности, отбрасывая элементы из начала исходной последовательности, пока предикат возвращает true. Как только предикат возвращает false для какого-либо элемента, drop-while
возвращает оставшуюся часть последовательности. Вот несколько примеров использования drop-while
:
- Отбрасывание чисел меньше 5:
(drop-while #(< % 5) [1 2 3 4 5 6 7 4 3])
; Результат: (5 6 7 4 3)
- Отбрасывание положительных чисел:
(drop-while pos? [3 2 1 0 -1 -2 3 4])
; Результат: (0 -1 -2 3 4)
- Работа со строками:
(apply str (drop-while #(not= % \space) "Hello World"))
; Результат: " World"
- Использование с бесконечными последовательностями:
(take 5 (drop-while #(< % 10) (range)))
; Результат: (10 11 12 13 14)
- Комбинирование с другими функциями:
(->> (range)
(map #(* % 2))
(drop-while #(< % 10))
(take 5))
; Результат: (10 12 14 16 18)
drop-while
особенно полезна, когда нужно пропустить начальные элементы последовательности, удовлетворяющие определенному условию, и начать обработку с первого элемента, не удовлетворяющего этому условию.
Функция partition
в Clojure используется для разбиения последовательности на подпоследовательности заданного размера. Вот несколько примеров использования partition
:
- Разбиение на группы по 3 элемента:
(partition 3 [1 2 3 4 5 6 7 8 9])
; Результат: ((1 2 3) (4 5 6) (7 8 9))
- Разбиение с шагом:
(partition 2 3 [1 2 3 4 5 6 7 8 9])
; Результат: ((1 2) (4 5) (7 8))
- Использование с заполнителем:
(partition 3 3 [:pad] [1 2 3 4 5 6 7 8 9 10])
; Результат: ((1 2 3) (4 5 6) (7 8 9) (10 :pad :pad))
- Работа со строками:
(partition 3 "abcdefghijklm")
; Результат: ((\a \b \c) (\d \e \f) (\g \h \i) (\j \k \l))
- Использование с бесконечными последовательностями:
(take 4 (partition 2 (range)))
; Результат: ((0 1) (2 3) (4 5) (6 7))
partition
полезна для группировки элементов, создания скользящих окон или разбиения данных на равные части для параллельной обработки.
some
: проверка наличия элемента, удовлетворяющего предикатуevery?
: проверка, удовлетворяют ли все элементы предикатуempty?
: проверка на пустотуdistinct?
: проверка на уникальность элементов
Функция some
в Clojure используется для проверки, удовлетворяет ли хотя бы один элемент коллекции заданному предикату. Вот несколько примеров использования some
:
- Проверка наличия четного числа в коллекции:
(def numbers [1 3 5 7 8 9])
(some even? numbers)
Этот код вернет true
, так как в коллекции есть четное число (8).
- Поиск первого элемента, удовлетворяющего условию:
(def fruits ["apple" "banana" "cherry" "date"])
(some #(when (.startsWith % "b") %) fruits)
Этот пример вернет "banana", так как это первый элемент, начинающийся с буквы "b".
- Проверка наличия элемента в множестве:
(def allowed-colors #{:red :green :blue})
(some allowed-colors [:yellow :green :purple])
Здесь some
вернет :green
, так как это первый элемент из последовательности, который присутствует в множестве allowed-colors
.
some
возвращает первое логически истинное (truthy) значение, возвращенное предикатом, или nil
, если ни один элемент не удовлетворяет условию. Это делает some
более гибким по сравнению с every?
, так как он может использоваться не только для проверки условия, но и для поиска конкретных элементов.
Функция every?
в Clojure используется для проверки, удовлетворяют ли все элементы коллекции определенному предикату. Вот пример использования every?
:
(def numbers [2 4 6 8 10])
(every? even? numbers)
Этот код вернет true
, так как все числа в векторе numbers
являются четными.
Другой пример:
(def toons ["Daffy Duck" "Bugs Bunny" "Elmer Fudd" "Yosemite Sam"])
(defn two-names? [name]
(= 2 (count (clojure.string/split name #" "))))
(every? two-names? toons)
Здесь every?
проверяет, состоит ли каждое имя персонажа из двух слов. Результат будет true
, так как все имена в векторе toons
состоят из двух слов.
every?
возвращает true
только если предикат возвращает true
для всех элементов коллекции. Если хотя бы для одного элемента предикат вернет false
, то every?
вернет false
.
Функция empty?
в Clojure используется для проверки, является ли коллекция пустой. Она возвращает true
, если коллекция не содержит элементов, и false
в противном случае. Вот несколько примеров использования empty?
:
- Проверка пустого вектора:
(empty? []) ; Возвращает true
(empty? [1 2 3]) ; Возвращает false
- Проверка пустой строки:
(empty? "") ; Возвращает true
(empty? "hello") ; Возвращает false
- Проверка пустого списка:
(empty? '()) ; Возвращает true
(empty? '(1 2 3)) ; Возвращает false
- Проверка пустого множества:
(empty? #{}) ; Возвращает true
(empty? #{1 2 3}) ; Возвращает false
- Использование в условных выражениях:
(defn greet [name]
(if (empty? name)
"Hello, stranger!"
(str "Hello, " name "!")))
(greet "") ; Возвращает "Hello, stranger!"
(greet "Alice") ; Возвращает "Hello, Alice!"
empty?
часто используется для проверки входных данных, управления потоком выполнения программы или в качестве условия в функциях высшего порядка, работающих с коллекциями.
Функция distinct?
в Clojure используется для проверки, являются ли все переданные аргументы уникальными. Вот несколько примеров использования distinct?
:
- Проверка уникальности отдельных аргументов:
(distinct? 1 2 3) ; Возвращает true
(distinct? 1 2 2) ; Возвращает false
- Проверка уникальности элементов в коллекции:
(apply distinct? [1 2 3]) ; Возвращает true
(apply distinct? [1 2 2 3]) ; Возвращает false
Обратите внимание, что при работе с коллекциями необходимо использовать apply
, чтобы передать элементы коллекции как отдельные аргументы функции distinct?
.
- Проверка уникальности символов:
(distinct? 'A 'B 'C) ; Возвращает true
(distinct? 'A 'A 'C) ; Возвращает false
- Работа с разными типами данных:
(distinct? 1 "a" :keyword) ; Возвращает true
(distinct? 1 1.0 "1") ; Возвращает true, так как это разные типы
Важно помнить, что distinct?
проверяет уникальность переданных ей аргументов, а не элементов внутри коллекции. Для проверки уникальности элементов коллекции без использования apply
можно создать вспомогательную функцию:
(defn coll-distinct? [coll]
(= (distinct coll) coll))
(coll-distinct? [1 2 3]) ; Возвращает true
(coll-distinct? [1 2 2 3]) ; Возвращает false
Эта вспомогательная функция сравнивает исходную коллекцию с результатом применения distinct
к ней.
sort
: сортировка элементовsort-by
: сортировка по ключуgroup-by
: группировка элементов по ключу
Функция sort
в Clojure используется для сортировки коллекций. Вот несколько способов использования sort
:
- Простая сортировка коллекции:
(sort [3 1 4 1 5 9 2 6])
; Результат: (1 1 2 3 4 5 6 9)
- Сортировка с использованием компаратора:
(sort > [3 1 4 1 5 9 2 6])
; Результат: (9 6 5 4 3 2 1 1)
- Сортировка разных типов данных:
(sort [22/7 2.71828 ##-Inf 1 55 3N])
; Результат: (##-Inf 1 2.71828 3N 22/7 55)
- Сортировка строк:
(sort ["aardvark" "boo" "a" "Antelope" "bar"])
; Результат: ("Antelope" "a" "aardvark" "bar" "boo")
- Сортировка с nil значениями:
(sort [2 6 nil 7 4])
; Результат: (nil 2 4 6 7)
Важно отметить, что sort
гарантированно стабилен, то есть равные элементы не будут переупорядочены. Функция sort
также может быть использована с другими типами коллекций, такими как списки и множества.
Функция sort-by
в Clojure используется для сортировки коллекции на основе значений, полученных путем применения заданной функции к каждому элементу. Вот несколько примеров использования sort-by
:
- Сортировка по длине строк:
(sort-by count ["aaa" "c" "bb" "dddd"])
; Результат: ("c" "bb" "aaa" "dddd")
- Сортировка структур данных по определенному полю:
(sort-by :age [{:name "Alice" :age 30} {:name "Bob" :age 25} {:name "Charlie" :age 35}])
; Результат: ({:name "Bob", :age 25} {:name "Alice", :age 30} {:name "Charlie", :age 35})
- Использование с компаратором:
(sort-by count > ["aaa" "c" "bb" "dddd"])
; Результат: ("dddd" "aaa" "bb" "c")
- Сортировка чисел по их абсолютному значению:
(sort-by #(Math/abs %) [-5 -1 3 2 -4])
; Результат: (-1 2 3 -4 -5)
- Сортировка по нескольким критериям:
(sort-by (juxt :last-name :first-name)
[{:first-name "John" :last-name "Doe"}
{:first-name "Jane" :last-name "Doe"}
{:first-name "Alice" :last-name "Smith"}])
; Результат: ({:first-name "Jane", :last-name "Doe"}
; {:first-name "John", :last-name "Doe"}
; {:first-name "Alice", :last-name "Smith"})
sort-by
особенно полезна, когда нужно сортировать сложные структуры данных или когда критерий сортировки не является прямым сравнением элементов.
Функция group-by
в Clojure используется для группировки элементов коллекции по результату применения заданной функции к каждому элементу. Результатом является ассоциативный массив, где ключи - это результаты применения функции, а значения - коллекции элементов, соответствующих этим результатам. Вот несколько примеров использования group-by
:
- Группировка чисел по четности:
(group-by even? [1 2 3 4 5 6])
; Результат: {false [1 3 5], true [2 4 6]}
- Группировка слов по первой букве:
(group-by #(first %) ["apple" "banana" "cherry" "date" "elderberry"])
; Результат: {\a ["apple"], \b ["banana"], \c ["cherry"], \d ["date"], \e ["elderberry"]}
- Группировка структур данных по полю:
(group-by :age [{:name "Alice" :age 25}
{:name "Bob" :age 30}
{:name "Charlie" :age 25}
{:name "David" :age 30}])
; Результат: {25 [{:name "Alice", :age 25} {:name "Charlie", :age 25}],
; 30 [{:name "Bob", :age 30} {:name "David", :age 30}]}
- Группировка по остатку от деления:
(group-by #(mod % 3) (range 10))
; Результат: {0 [0 3 6 9], 1 [1 4 7], 2 [2 5 8]}
- Использование с более сложной функцией группировки:
(group-by #(cond
(< % 0) :negative
(> % 0) :positive
:else :zero)
[-2 -1 0 1 2 3 -4])
; Результат: {:negative [-2 -1 -4], :positive [1 2 3], :zero [0]}
group-by
особенно полезна для анализа данных, создания индексов или подготовки данных для дальнейшей обработки.
get
: получение элемента по индексуassoc
: замена элемента по индексуsubvec
: получение подвектора
Функция get
в Clojure может использоваться для получения элемента вектора по его индексу. Вот несколько примеров использования get
с векторами:
- Получение элемента по индексу:
(get [10 20 30 40 50] 2)
; Результат: 30
- Использование с нулевым индексом:
(get ["a" "b" "c"] 0)
; Результат: "a"
- Получение последнего элемента:
(get [1 2 3 4 5] 4)
; Результат: 5
- Использование значения по умолчанию:
(get [1 2 3] 5 :not-found)
; Результат: :not-found
- Работа с пустым вектором:
(get [] 0)
; Результат: nil
get
безопасно работает с индексами за пределами вектора, возвращая nil
(или указанное значение по умолчанию), что делает его удобным для использования в случаях, когда индекс может быть недопустимым.
Функция assoc
в Clojure может использоваться с векторами для замены элемента по указанному индексу. Вот несколько примеров использования assoc
с векторами:
- Замена элемента по индексу:
(assoc [0 1 2 3 4] 2 :x)
; Результат: [0 1 :x 3 4]
- Замена первого элемента:
(assoc ["a" "b" "c"] 0 "z")
; Результат: ["z" "b" "c"]
- Замена последнего элемента:
(assoc [1 2 3 4] 3 5)
; Результат: [1 2 3 5]
- Добавление нового элемента в конец вектора:
(assoc [1 2 3] 3 4)
; Результат: [1 2 3 4]
- Замена нескольких элементов одновременно:
(assoc [0 1 2 3 4] 1 :a 3 :b)
; Результат: [0 :a 2 :b 4]
Важно отметить, что assoc
создает новый вектор, не изменяя исходный. Также, при использовании assoc
с векторами, нельзя добавлять элементы с индексом больше, чем текущая длина вектора плюс один, иначе будет выброшено исключение.
Функция subvec
в Clojure используется для создания подвектора из существующего вектора. Она позволяет извлечь часть вектора, указав начальный и (опционально) конечный индексы. Вот несколько примеров использования subvec
:
- Получение подвектора с указанием начального и конечного индексов:
(subvec [0 1 2 3 4 5] 2 4)
; Результат: [2 3]
- Получение подвектора от указанного индекса до конца:
(subvec [10 20 30 40 50] 3)
; Результат: [40 50]
- Получение пустого подвектора:
(subvec [1 2 3] 2 2)
; Результат: []
- Использование с отрицательными индексами (вызовет исключение):
(subvec [1 2 3 4 5] -1 3)
; Вызовет IndexOutOfBoundsException
- Получение всего вектора:
(subvec [1 2 3 4 5] 0)
; Результат: [1 2 3 4 5]
subvec
создает новый вектор, который является представлением части исходного вектора. Это эффективная операция, так как она не копирует элементы, а создает вид на существующий вектор.
disj
: удаление элементаcontains?
: проверка наличия элемента
Функция disj
в Clojure используется для удаления одного или нескольких элементов из множества. Она возвращает новое множество без указанных элементов. Вот несколько примеров использования disj
с множествами:
- Удаление одного элемента из множества:
(disj #{1 2 3 4 5} 3)
; Результат: #{1 2 4 5}
- Удаление нескольких элементов:
(disj #{:a :b :c :d} :b :c)
; Результат: #{:a :d}
- Попытка удаления элемента, которого нет в множестве:
(disj #{1 2 3} 4)
; Результат: #{1 2 3}
- Использование с пустым множеством:
(disj #{} 1)
; Результат: #{}
- Удаление всех элементов:
(disj #{1 2 3} 1 2 3)
; Результат: #{}
disj
создает новое множество, не изменяя исходное. Если указанный для удаления элемент отсутствует в множестве, функция просто возвращает исходное множество без изменений.
Функция contains?
в Clojure может использоваться с множествами для проверки наличия элемента. Вот несколько примеров использования contains?
с множествами:
- Проверка наличия элемента в множестве:
(contains? #{:a :b :c} :b)
; Результат: true
- Проверка отсутствия элемента:
(contains? #{:a :b :c} :d)
; Результат: false
- Работа с числовыми множествами:
(contains? #{1 2 3 4 5} 3)
; Результат: true
- Проверка с пустым множеством:
(contains? #{} :a)
; Результат: false
Важно отметить, что contains?
работает эффективно с множествами, так как они являются хешированными коллекциями. Это означает, что проверка выполняется за константное или логарифмическое время, в отличие от линейного поиска по значениям.
Функция contains?
для множеств фактически проверяет наличие ключа, но поскольку в множествах ключи и значения совпадают, это эквивалентно проверке наличия значения.
assoc
: добавление пары ключ-значениеdissoc
: удаление пары по ключуselect-keys
: выборка по ключамmerge
: объединение нескольких map'ов
Функция assoc
в Clojure используется с ассоциативными массивами для добавления новых пар ключ-значение или обновления существующих. Вот несколько примеров использования assoc
с ассоциативными массивами:
- Добавление новой пары ключ-значение:
(assoc {:a 1 :b 2} :c 3)
; Результат: {:a 1, :b 2, :c 3}
- Обновление существующего значения:
(assoc {:name "John" :age 30} :age 31)
; Результат: {:name "John", :age 31}
- Добавление нескольких пар одновременно:
(assoc {:a 1} :b 2 :c 3 :d 4)
; Результат: {:a 1, :b 2, :c 3, :d 4}
- Использование с пустым ассоциативным массивом:
(assoc {} :key "value")
; Результат: {:key "value"}
- Обновление вложенных структур:
(assoc {:user {:name "Alice" :age 25}}
:user {:name "Alice" :age 26})
; Результат: {:user {:name "Alice", :age 26}}
assoc
создает новый ассоциативный массив, не изменяя исходный. Это соответствует принципу неизменяемости данных в Clojure.
Функция dissoc
в Clojure используется с ассоциативными массивами для удаления одной или нескольких пар ключ-значение. Она возвращает новый ассоциативный массив без указанных ключей. Вот несколько примеров использования dissoc
:
- Удаление одного ключа:
(dissoc {:a 1 :b 2 :c 3} :b)
; Результат: {:a 1, :c 3}
- Удаление нескольких ключей:
(dissoc {:name "John" :age 30 :city "New York" :country "USA"} :age :country)
; Результат: {:name "John", :city "New York"}
- Попытка удаления несуществующего ключа:
(dissoc {:a 1 :b 2} :c)
; Результат: {:a 1, :b 2}
- Удаление всех ключей:
(dissoc {:a 1 :b 2} :a :b)
; Результат: {}
- Использование с пустым ассоциативным массивом:
(dissoc {} :key)
; Результат: {}
dissoc
создает новый ассоциативный массив, не изменяя исходный, что соответствует принципу неизменяемости данных в Clojure.
Функция select-keys
в Clojure используется для создания нового ассоциативного массива, содержащего только указанные ключи из исходного ассоциативного массива. Вот несколько примеров использования select-keys
:
- Выбор нескольких ключей:
(select-keys {:a 1 :b 2 :c 3 :d 4} [:a :c])
; Результат: {:a 1, :c 3}
- Выбор одного ключа:
(select-keys {:name "John" :age 30 :city "New York"} [:name])
; Результат: {:name "John"}
- Выбор несуществующих ключей:
(select-keys {:a 1 :b 2} [:c :d])
; Результат: {}
- Выбор всех существующих ключей:
(select-keys {:x 10 :y 20} [:x :y :z])
; Результат: {:x 10, :y 20}
- Использование с пустым ассоциативным массивом:
(select-keys {} [:a :b])
; Результат: {}
select-keys
создает новый ассоциативный массив, содержащий только выбранные ключи и их значения из исходного массива. Эта функция полезна для фильтрации данных или создания подмножества ассоциативного массива с нужными ключами.
Функция merge
в Clojure используется для объединения двух или более ассоциативных массивов в один новый. При этом, если ключи повторяются, значения из последующих массивов перезаписывают значения из предыдущих. Вот несколько примеров использования merge
:
- Объединение двух ассоциативных массивов:
(merge {:a 1 :b 2} {:c 3 :d 4})
; Результат: {:a 1, :b 2, :c 3, :d 4}
- Перезапись значений при конфликте ключей:
(merge {:a 1 :b 2} {:b 3 :c 4})
; Результат: {:a 1, :b 3, :c 4}
- Объединение нескольких ассоциативных массивов:
(merge {:x 1} {:y 2} {:z 3})
; Результат: {:x 1, :y 2, :z 3}
- Использование с пустым ассоциативным массивом:
(merge {} {:a 1} {:b 2})
; Результат: {:a 1, :b 2}
- Объединение с nil:
(merge {:a 1} nil {:b 2})
; Результат: {:a 1, :b 2}
merge
создает новый ассоциативный массив, не изменяя исходные, что соответствует принципу неизменяемости данных в Clojure. Эта функция особенно полезна для объединения конфигураций, обновления состояния или слияния данных из разных источников.
future
: асинхронное выполнениеatom
,ref
,agent
: управление состоянием
Clojure предоставляет несколько механизмов для управления состоянием и конкурентностью: atom, ref, agent и future. Каждый из них имеет свои особенности и применяется в различных сценариях.
Atom используется для управления одиночным, независимым значением.
(def counter (atom 0))
(swap! counter inc) ; Увеличение значения на 1
(reset! counter 10) ; Установка нового значения
@counter ; Получение текущего значения
Future используется для асинхронного выполнения задач.
(def long-running-task
(future
(Thread/sleep 5000)
(println "Task completed")
42))
(deref long-running-task) ; Блокирующее ожидание результата
@long-running-task ; Сокращенная форма deref
(realized? long-running-task) ; Проверка завершения
Ref используется для координированных изменений нескольких значений в рамках транзакции.
(def account1 (ref 1000))
(def account2 (ref 500))
(dosync
(alter account1 - 100)
(alter account2 + 100))
@account1 ; 900
@account2 ; 600
Agent используется для асинхронных, независимых изменений состояния.
(def counter (agent 0))
(send counter inc) ; Асинхронное увеличение
(await counter) ; Ожидание завершения всех отправленных действий
@counter ; Получение текущего значения
Эти механизмы позволяют эффективно управлять состоянием и конкурентностью в Clojure, обеспечивая безопасность и предсказуемость при работе с изменяемыми данными.
(defmacro my-macro [arg]
`(println ~arg))
Написание макроса в Clojure включает следующие основные шаги:
- Определение макроса с помощью специальной формы
defmacro
:
(defmacro name [params*]
body)
- Использование синтаксиса для построения кода внутри макроса:
- Обратная кавычка ` для создания шаблона кода
- Тильда ~ для вставки значений параметров
- Тильда с собачкой ~@ для развертывания последовательностей
- Возврат формы кода, которая будет вставлена на место вызова макроса
Пример простого макроса:
(defmacro my-when [test & body]
`(if ~test
(do ~@body)))
Этот макрос создает условную конструкцию, которая выполняет тело только если тест истинен.
При написании макросов важно помнить:
- Макросы выполняются на этапе компиляции
- Аргументы макроса не вычисляются до передачи в тело макроса
- Используйте функцию
macroexpand-1
для отладки макросов
Макросы - мощный инструмент метапрограммирования в Clojure, позволяющий расширять синтаксис языка и создавать предметно-ориентированные конструкции.
(.method object args)
(Class/staticMethod args)
(new ClassName args)
Clojure обеспечивает тесную интеграцию с Java, что позволяет легко использовать Java-классы и библиотеки в Clojure-коде. Вот основные аспекты взаимодействия Clojure с Java:
Для использования Java-классов в Clojure их нужно импортировать:
(import java.util.UUID)
(UUID/randomUUID)
Создание экземпляров Java-классов в Clojure выглядит так:
(java.util.HashSet. ["1" "2"])
Методы Java-объектов вызываются с помощью точки:
(.length "hello world")
Для реализации Java-интерфейсов используется макрос reify
:
(reify java.io.FilenameFilter
(accept [this dir name]
true))
Для вызова Clojure-кода из Java используются классы RT
и Var
:
RT.loadResourceScript("script.clj");
Var fn = RT.var("namespace", "function");
fn.invoke(1, 2);
Clojure использует Java-типы для представления примитивных значений:
(class 1) ; java.lang.Long
(class "string") ; java.lang.String
(class true) ; java.lang.Boolean
Эта тесная интеграция позволяет эффективно использовать существующие Java-библиотеки и инфраструктуру при разработке на Clojure, сочетая мощь JVM с выразительностью функционального программирования.
В Clojure ключевые слова (keywords) часто используются в функциях для создания более гибких и выразительных интерфейсов. Вот несколько примеров использования ключевых слов в функциях Clojure:
(defn greet [& {:keys [name greeting] :or {greeting "Hello"}}]
(str greeting ", " (or name "World") "!"))
(greet) ; => "Hello, World!"
(greet :name "Alice") ; => "Hello, Alice!"
(greet :greeting "Hi" :name "Bob") ; => "Hi, Bob!"
В этом примере функция greet
использует ключевые слова для именованных аргументов, что делает вызов функции более читаемым и гибким.
Ключевые слова в Clojure могут сами выступать в роли функций для поиска значений в ассоциативных массивах:
(def user {:name "Hubert" :alias "mrhaki"})
(:name user) ; => "Hubert"
(:alias user) ; => "mrhaki"
(:city user) ; => nil
Это позволяет элегантно извлекать значения из карт.
(defn process-user [{:keys [name location]}]
(println name "lives in" location))
(process-user {:name "Alice" :location "Wonderland"})
; Выведет: Alice lives in Wonderland
Здесь ключевые слова используются для деструктуризации входной карты, что упрощает доступ к её содержимому.
(defn create-user [username & {:keys [email age] :or {age 18}}]
{:username username
:email email
:age age})
(create-user "john_doe" :email "[email protected]")
; => {:username "john_doe", :email "[email protected]", :age 18}
В этом примере функция create-user
принимает обязательный аргумент username
и опциональные именованные аргументы :email
и :age
.
Использование ключевых слов в функциях Clojure позволяет создавать более гибкие и понятные интерфейсы, особенно когда функция имеет множество опциональных параметров.
Работа с исключениями в Clojure основана на механизмах Java, но имеет свои особенности:
try/catch/finally:
(try
(/ 1 0)
(catch ArithmeticException e
(println "Caught arithmetic exception:" (ex-message e)))
(catch Exception e
(println "Caught general exception:" (ex-message e)))
(finally
(println "This always executes")))
Множественные catch блоки:
(try
(+ 1 nil)
(catch ArithmeticException e
(println "Arithmetic error"))
(catch NullPointerException e
(println "Null pointer error"))
(catch Throwable e
(println "Caught any throwable")))
ex-info и ex-data:
(try
(throw (ex-info "My error" {:type :custom-error}))
(catch clojure.lang.ExceptionInfo e
(let [data (ex-data e)]
(println "Error type:" (:type data)))))
Функции для работы с исключениями:
ex-message
: получение сообщения исключенияex-cause
: получение причины исключения
slingshot: Предоставляет расширенные возможности для работы с исключениями:
(use '[slingshot.slingshot :only [try+ throw+]])
(try+
(throw+ {:type :my-error :message "Oops!"})
(catch [:type :my-error] {:keys [message]}
(println "Caught error:" message)))
dire: Позволяет декларативно описывать обработку исключений для функций:
(require '[dire.core :refer [with-handler!]])
(defn my-function [x]
(/ 1 x))
(with-handler! #'my-function
java.lang.ArithmeticException
(fn [e & args]
(println "Cannot divide by zero")))
Clojure предоставляет гибкие инструменты для обработки исключений, сочетая возможности Java с функциональным подходом.
Работа с JDBC в Clojure осуществляется с помощью библиотеки clojure.java.jdbc (или ее современного аналога next.jdbc). Вот основные аспекты работы с JDBC в Clojure:
Для подключения к базе данных используется спецификация в виде карты:
(def db-spec
{:dbtype "postgresql"
:dbname "mydb"
:user "myuser"
:password "mypassword"})
Основные функции для работы с базой данных:
execute!
: выполнение SQL-запросовquery
: выполнение SELECT-запросовinsert!
: вставка данныхupdate!
: обновление данныхdelete!
: удаление данных
Пример выполнения запроса:
(jdbc/query db-spec ["SELECT * FROM users WHERE id = ?" 1])
Для работы с транзакциями используется макрос with-db-transaction
:
(jdbc/with-db-transaction [t-con db-spec]
(jdbc/insert! t-con :users {:name "Alice"})
(jdbc/update! t-con :users {:balance 100} ["name = ?" "Alice"]))
Макрос with-db-connection
позволяет переиспользовать соединение:
(jdbc/with-db-connection [conn db-spec]
(let [users (jdbc/query conn ["SELECT * FROM users"])]
(jdbc/insert! conn :logs {:action "Users fetched"})))
Для оптимизации производительности используются подготовленные выражения:
(jdbc/execute! db-spec ["INSERT INTO users (name, email) VALUES (?, ?)" "Bob" "[email protected]"])
Работа с JDBC в Clojure предоставляет удобный и идиоматичный способ взаимодействия с реляционными базами данных, сочетая мощь SQL с выразительностью Clojure.
Для работы с JPA в Clojure существует несколько подходов:
clj-jpa - это библиотека, которая предоставляет удобный интерфейс для работы с JPA в Clojure. Чтобы начать использовать clj-jpa, нужно добавить ее в зависимости проекта:
[clj-jpa "0.1.0-SNAPSHOT"]
Также необходимо добавить реализацию JPA, например Hibernate.
Clojure позволяет напрямую работать с Java-классами и интерфейсами. Можно использовать стандартные классы JPA, такие как EntityManager, EntityManagerFactory и т.д.:
(import javax.persistence.Persistence)
(def emf (Persistence/createEntityManagerFactory "myPersistenceUnit"))
(def em (.createEntityManager emf))
Можно создать макросы, которые будут генерировать код для работы с JPA, делая его более идиоматичным для Clojure:
(defmacro with-transaction [& body]
`(let [tx# (.getTransaction em)]
(try
(.begin tx#)
(let [result# (do ~@body)]
(.commit tx#)
result#)
(catch Exception e#
(.rollback tx#)
(throw e#)))))
Некоторые Clojure-библиотеки для работы с базами данных, такие как Korma или HoneySQL, могут быть интегрированы с JPA для предоставления более функционального и идиоматичного интерфейса.
При работе с JPA в Clojure важно учитывать особенности взаимодействия между Clojure и Java, такие как преобразование типов данных и управление транзакциями.
Для создания HTTP сервера в Clojure обычно используется библиотека Ring, которая предоставляет низкоуровневый API для обработки HTTP-запросов и ответов. Вот основные шаги для создания простого HTTP сервера в Clojure:
- Добавьте зависимости в файл project.clj:
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-core "1.9.5"]
[ring/ring-jetty-adapter "1.9.5"]]
- Импортируйте необходимые библиотеки в вашем файле core.clj:
(ns your-project.core
(:require [ring.adapter.jetty :as jetty]))
Определите функцию-обработчик, которая будет принимать запрос и возвращать ответ:
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello World"})
Используйте функцию run-jetty для запуска сервера:
(defn -main [& args]
(jetty/run-jetty handler {:port 3000 :join? false}))
- Для маршрутизации запросов можно использовать библиотеку Compojure:
(ns your-project.core
(:require [compojure.core :refer :all]
[compojure.route :as route]))
(defroutes app
(GET "/" [] "Hello World")
(route/not-found "Not Found"))
(defn -main [& args]
(jetty/run-jetty app {:port 3000 :join? false}))
- Для асинхронной обработки запросов и поддержки веб-сокетов можно использовать библиотеку http-kit:
(ns your-project.core
(:require [org.httpkit.server :as server]))
(defn app [req]
{:status 200
:headers {"Content-Type" "text/html"}
:body "hello HTTP!"})
(defn -main [& args]
(server/run-server app {:port 8080}))
Создание HTTP сервера в Clojure с использованием Ring и других библиотек позволяет легко разрабатывать веб-приложения, используя функциональный подход и мощные возможности языка.
Для работы с графическими компонентами Swing из Clojure можно использовать следующие подходы:
Clojure позволяет напрямую импортировать и использовать Java-классы Swing:
(import [javax.swing JFrame JLabel])
(def frame (JFrame. "My Window"))
(def label (JLabel. "Hello Swing!"))
(.add frame label)
(.setSize frame 300 200)
(.setVisible frame true)
Библиотека clj.swing предоставляет обертки для многих компонентов и методов из пакетов java.awt и javax.swing:
(ns my-app.core
(:require [clj.swing :as swing]))
(def frame (swing/frame {:title "My Window"}))
(def label (swing/label "Hello Swing!"))
(swing/add frame label)
(swing/show! frame)
Можно создать макросы для более декларативного описания интерфейса:
(defmacro with-swing [& body]
`(SwingUtilities/invokeLater
(fn [] ~@body)))
(with-swing
(let [frame (JFrame. "My App")
button (JButton. "Click me")]
(.add frame button)
(.pack frame)
(.setVisible frame true)))
Clojure можно использовать для расширения существующих Java Swing приложений, добавляя новую функциональность или изменяя поведение компонентов.
При работе со Swing из Clojure важно помнить о многопоточности и использовать SwingUtilities/invokeLater для обновления GUI из других потоков. Также стоит обратить внимание на управление ресурсами и правильное закрытие окон.
(ns html-dsl.core)
(defn- escape-html [text]
(clojure.string/escape text
{\< "<"
\> ">"
\& "&"
\" """
\' "'"}))
(defn- attr-str [attrs]
(if (empty? attrs)
""
(str " " (clojure.string/join " "
(for [[k v] attrs]
(str (name k) "=\"" (escape-html (str v)) "\""))))))
(defn tag [name attrs & content]
(str "<" (clojure.string/lower-case (name name))
(attr-str attrs)
(if (empty? content)
"/>"
(str ">" (apply str content) "</" (clojure.string/lower-case (name name)) ">"))))
(defmacro html [& content]
`(str "<!DOCTYPE html>"
(tag :html {} ~@content)))
(defmacro defelem [name & args]
`(defmacro ~name ~@args
(list 'tag '~name ~@args)))
(defelem head [& content])
(defelem body [& content])
(defelem div [& content])
(defelem p [& content])
(defelem a [& content])
(defelem h1 [& content])
(defelem ul [& content])
(defelem li [& content])
Теперь можно использовать этот DSL для создания HTML-структур:
(ns my-app.core
(:require [html-dsl.core :refer :all]))
(def my-page
(html
(head
(tag :title {} "My Awesome Page"))
(body
(h1 {} "Welcome to My Page")
(p {} "This is a paragraph.")
(div {:class "content"}
(ul {}
(li {} "Item 1")
(li {} "Item 2")
(li {} "Item 3"))
(p {}
"Check out "
(a {:href "https://clojure.org"} "Clojure's website"))))))
(println my-page)
Этот код создаст следующий HTML:
<!DOCTYPE html><html><head><title>My Awesome Page</title></head><body><h1>Welcome to My Page</h1><p>This is a paragraph.</p><div class="content"><ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul><p>Check out <a href="https://clojure.org">Clojure's website</a></p></div></body></html>
Этот DSL позволяет создавать HTML-структуры с помощью вложенных функций и макросов, что делает код более читаемым и близким к структуре HTML. Вы можете расширить этот DSL, добавив больше элементов HTML или дополнительные функции для работы со стилями и атрибутами.
(ns css-dsl.core)
(defn css-rule [selector properties]
(let [props-str (clojure.string/join "; "
(map (fn [[k v]] (str (name k) ": " v)) properties))]
(str selector " { " props-str " }")))
(defn css [rules]
(clojure.string/join "\n" rules))
(defmacro nested-css [selector & body]
`(let [nested-rules# (partition 2 (quote ~body))
formatted-rules# (map (fn [[sel# props#]]
(css-rule (str ~selector " " sel#) props#))
nested-rules#)]
(clojure.string/join "\n" formatted-rules#)))
(def stylesheet
(css
[(css-rule "body" {:margin "0" :font-family "Arial, sans-serif"})
(css-rule ".header" {:background-color "#333" :color "white" :padding "10px"})
(nested-css ".content"
h1 {:color "#444" :font-size "28px"}
p {:color "#666" :line-height "1.6"})
(css-rule ".footer" {:background-color "#222"
:color "white"
:text-align "center"
:padding "10px"})]))
(println stylesheet)
Этот DSL для CSS на Clojure позволяет создавать стили с помощью функций и макросов, что делает код более читаемым и близким к структуре CSS. Вы можете использовать css-rule
для создания отдельных правил CSS, css
для объединения нескольких правил в таблицу стилей, и nested-css
для создания вложенных правил.
Вот пример реализации простого DSL для SQL на Clojure:
(ns sql-dsl.core
(:require [clojure.string :as str]))
(defn- format-where [conditions]
(when (seq conditions)
(str " WHERE " (str/join " AND " (map (fn [[k v]] (str (name k) " = ?")) conditions)))))
(defn- format-set [values]
(str/join ", " (map (fn [[k v]] (str (name k) " = ?")) values)))
(defn select [table & {:keys [columns where]}]
(let [cols (if (seq columns) (str/join ", " (map name columns)) "*")]
(str "SELECT " cols " FROM " (name table)
(format-where where))))
(defn insert [table values]
(let [cols (str/join ", " (map name (keys values)))
vals (str/join ", " (repeat (count values) "?"))]
(str "INSERT INTO " (name table) " (" cols ") VALUES (" vals ")")))
(defn update [table values & {:keys [where]}]
(str "UPDATE " (name table)
" SET " (format-set values)
(format-where where)))
(defn delete [table & {:keys [where]}]
(str "DELETE FROM " (name table)
(format-where where)))
;; Пример использования
(defn example-queries []
(println (select :users :columns [:id :name :email] :where {:active true}))
(println (insert :users {:name "John" :email "[email protected]"}))
(println (update :users {:email "[email protected]"} :where {:id 1}))
(println (delete :users :where {:id 1})))
(example-queries)
Этот DSL позволяет создавать базовые SQL-запросы (SELECT, INSERT, UPDATE, DELETE) с помощью функций Clojure. Вот что выведет example-queries
:
SELECT id, name, email FROM users WHERE active = ?
INSERT INTO users (name, email) VALUES (?, ?)
UPDATE users SET email = ? WHERE id = ?
DELETE FROM users WHERE id = ?
Этот DSL использует подготовленные выражения (prepared statements) для безопасной работы с параметрами. Вы можете расширить этот DSL, добавив поддержку JOIN, ORDER BY, LIMIT и других SQL-конструкций.