Краткий экскурс в синтаксис Clojure, который настолько лаконичен, что вы сможете прочитать этот раздел примерно за 15 минут (или меньше).

Комментарии

;; две точки с запятой для комментария строки, ; одна точка с запятой для комментария остальной части строки.

#_ макрос чтения комментариев, чтобы закомментировать следующую форму.

(comment ) форма для комментариев ко всем содержащимся формам.

Clojure записан в формах

Clojure написан в "формах", которые представляют собой списки элементов, заключенных в круглые скобки () и разделенных пробелами.

Clojure считает первое значение в форме вызовом функции. Дополнительные значения в форме передаются в качестве аргументов вызываемой функции.

Clojure организован в виде одного или нескольких пространств имен. Пространство имен представляет собой путь к каталогу и имя файла, который содержит код конкретного пространства имен.

;; Define the namespace test
(ns test.code)  ;; src/test/code.clj

;; Define a longer namespace
(ns com.company.product.component.core)  ;; src/com/company/product/component/core.clj

Манипулирование строками

Функция str создает новую строку из всех переданных аргументов.

(str "Hello" " " "World")
; => "Hello World"

clojure.string возвращает строковые значения (другие функции возвращают символы в качестве результата).

Math (математика), Truth (истина) и префиксная нотация

Функции используют префиксную нотацию, поэтому вы можете легко выполнять математические вычисления с несколькими значениями.

(+ 1 2 3 5 7 9 12) ; => 40
(- 24 7 3) ; => 14
(* 1 2) ; => 2
(/ 27 7) ; => 22/7

Математический метод очень точен, нет необходимости в правилах предшествования операторов (так как операторов нет).

Благодаря вложенным формам вычисления очень точны.

(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

Равенство — это =

(= 1 1) ; => true
(= 2 1) ; => false

; вам нужно использовать в логике not, так  (not true) ; => false

(not= true false) ; => true

Типы

Clojure имеет строгую типизацию, поэтому в Clojure все является типом.

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

Поскольку Clojure является размещаемым на уже существующей платформе языком, он использует систему типов используемой среды, где это уместно. Например, Clojure под капотом применяет объектные типы Java для булевых данных, строк и чисел.

Для проверки типа кода в Clojure используйте функции class или type.

(class 1) ; Integer literals are java.lang.Long by default
(class 1.); Float literals are java.lang.Double
(class ""); Strings always double-quoted, and are java.lang.String
(class false) ; Booleans are java.lang.Boolean
(class nil); The "null" value is called nil

Vectors (векторы) и Lists (списки) — это тоже классы java!

(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList

Коллекции и последовательности

Наиболее распространенные коллекции данных в Clojure:

  • (1 2 "three") или (list 1 2 "three") — список значений, прочитанный от начала до конца (последовательный доступ).

  • [1 2 "three"] или (list 1 2 "three") — вектор значений с индексом (произвольный доступ).

  • {:key "value"} или (hash-map :key "value") — хэштаблица (хэшмапа) с нулем или более пар ключ-значение (ассоциативная связь).

  • #{1 2 "three"} или (set 1 2 "three") — уникальный набор значений.

Список () определяется как вызов функции. Первый элемент списка — имя вызываемой функции, а дополнительные значения являются аргументами функции.

Функция ' quote сообщает читателю Clojure, что список следует рассматривать только как данные.

'(1 2 3)

Списки и векторы — это коллекции.

(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true

Только списки являются последовательностями.

(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false

Последовательности — это интерфейс для логических списков, которые могут быть ленивыми. "Ленивая" означает, что последовательность значений не оценивается до тех пор, пока к ней не обратятся.

Ленивая последовательность позволяет использовать большие или даже бесконечные серии, например, как:

 (range) ; => (0 1 2 3 4 ...) - an infinite series
(take 4 (range)) ;  (0 1 2 3) - lazyily evaluate range and stop when enough values are taken

Используйте cons для добавления элемента в начало списка или вектора.

(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)

Используйте conj, чтобы добавить элемент относительно типа коллекции, в начало списка или в конец вектора.

(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)

Используйте concat для сложения списков или векторов вместе.

(concat [1 2] '(3 4)) ; => (1 2 3 4)

Используйте filter, map для взаимодействия с коллекциями.

(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)

Используйте reduce для их уменьшения.

(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10

Reduce также может принимать аргумент начального значения.

(reduce conj [] '(3 2 1))
; => [3 2 1]

Эквивалент (conj (conj (conj (conj [] 3) 2) 1)

Функции

Используйте fn для создания новых функций, определяющих какое-то поведение. fn называют анонимной функцией, поскольку она не имеет внешнего имени, на которое можно сослаться, и ее нужно вызывать в форме списка.

(fn hello [] "Hello World") ; => hello

Оберните форму (fn ,,,) в круглые скобки, чтобы вызвать ее и вернуть результат.

((fn hello [] "Hello World")) ; => "Hello World"

Создайте многократно используемую функцию с помощью def, создав имя, которым будет var. Поведение функции, определенное в def, может быть изменено, а выражение переоценено для использования нового поведения.

(defn hello-world []
  "Hello World")
;; => "Hello World"

[] — это список аргументов для функции.

(defn hello [name]
  (str "Hello " name))
(hello "Steve") ; => "Hello Steve"

Clojure поддерживает мультивариадические функции, позволяя одному определению функции отвечать на вызов функции с разным количеством аргументов.

(defn hello3
  ([] "Hello World")
  ([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"

Функции могут упаковывать дополнительные аргументы в последовательность.

(defn count-args [& args]
  (str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"

Вы можете смешивать обычные и упакованные аргументы.

(defn hello-count [name & args]
  (str "Hello " name ", you passed " (count args) " extra args"))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"

Коллекции хэшмапов (hash-map)

(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap

Ключевые слова подобны строкам с небольшим бонусом в плане эффективности.

(class :a) ; => clojure.lang.Keyword

Мапы могут использовать любой тип в качестве ключа, но как правило, лучше всего подходят ключевые слова.

(def stringmap (hash-map "a" 1, "b" 2, "c" 3))
stringmap  ; => {"a" 1, "b" 2, "c" 3}

(def keymap (hash-map :a 1 :b 2 :c 3))
keymap ; => {:a 1, :c 3, :b 2} (order is not guaranteed)

Запятые — это пробелы. Запятые всегда рассматриваются как пробелы и игнорируются Clojure-ридером.

Получение значения из мапы путем вызова ее как функции.

(stringmap "a") ; => 1
(keymap :a) ; => 1

Ключевые слова позволяют извлекать их значение из мапы. Строки так не используются.

(:b keymap) ; => 2

("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn

При извлечении значения, которое не было представлено, возвращается nil.

(stringmap "d") ; => nil

Используйте assoc для добавления новых ключей в хэшмапы.

(assoc keymap :d 4) ; => {:a 1, :b 2, :c 3, :d 4}

Но помните, что типы clojure иммутабельны!

keymap ; => {:a 1, :b 2, :c 3}

Используйте dissoc для удаления ключей.

(dissoc keymap :a :b) ; => {:c 3}

Установки

(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}

Добавьте элемент с помощью conj.

(conj #{1 2 3} 4) ; => #{1 2 3 4}

Удалите его с помощью disj.

(disj #{1 2 3} 1) ; => #{2 3}
````

Test for existence by using the set as a function:

```clojure
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil

В пространстве имен clojure.sets есть больше функций.

Полезные формы

Логические конструкции в clojure — это просто макросы, и выглядят они так же, как и все остальное.

(if false "a" "b") ; => "b"
(if false "a") ; => nil

Используйте let для создания временных привязок.

(let [a 1 b 2]
  (> a b)) ; => false

Группируйте утверждения вместе с помощью do.

(do
  (print "Hello")
  "World") ; => "World" (prints "Hello")

Функции имеют неявный do.

(defn print-and-say-hello [name]
  (print "Saying hello to " name)
  (str "Hello " name))
(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")

Так же как и let.

(let [name "Urkel"]
  (print "Saying hello to " name)
  (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")

Пространства имен и библиотеки

Пространства имен используются для того, чтобы организовать код в логические группы. В верхней части каждого файла Clojure есть форма ns, которая определяет название пространства имен. Доменная часть названия пространства имен обычно является наименованием организации или сообщества (например, GitHub user/organisation).

(ns domain.namespace-name)

Все проекты Practicalli имеют домены пространства имен practicalli

(ns practicalli.service-name)

require позволяет коду из одного пространства имен быть доступным из другого пространства имен, либо из того же проекта Clojure, либо из библиотеки, добавленной в classpath (путь к классам) проекта.

Директива :as в require используется для указания имени псевдонима, которое сокращенно обозначает полное имя библиотеки.

Или :refer [function-name var-name] может использоваться для указания конкретных функций и данных (vars), которые доступны напрямую.

Требуемая (required) директива обычно добавляется к форме пространства имен.

(ns practicalli.service-name
  (require [clojure.set :as set]))

Функции из clojure.set можно использовать через псевдоним, а не через полное имя, т.е. clojure.set/intersection

(set/intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
(set/difference #{1 2 3} #{2 3 4}) ; => #{1}

Директива :require может быть использована для включения нескольких пространств имен библиотек.

(ns test
  (:require
    [clojure.string :as string]
    [clojure.set :as set]))

require может использоваться самостоятельно, обычно в блоке многофункционального кода.

(comment
  (require 'clojure.set :as set))

Java

В Java есть огромная и полезная стандартная библиотека, поэтому вы захотите узнать, как с ней работать.

Используйте import для загрузки пакета java.

(import java.util.Date)

Или импорт из имени пакета java.

(ns test
  (:import
    java.util.Date
    java.util.Calendar))

Используйте имя класса с символом "." в конце, чтобы создать новый экземпляр.

(Date.) ; <a date object>

Используйте . для вызова методов. Или используйте горячую клавишу ".method".

(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.))  ; exactly the same thing.

Используйте / для вызова статических методов.

(System/currentTimeMillis) ; <a timestamp> (system is always present)

Используйте doto, чтобы сделать ощущения от взаимодействия с (мутабельными [изменяемыми]) классами более толерантными.

(import java.util.Calendar)
(doto (Calendar/getInstance)
  (.set 2000 1 1 0 0 0)
  .getTime) ; => A Date. set to 2000-01-01 00:00:00

Язык программирования Clojure — это язык программирования общего назначения, на нём можно разрабатывать абсолютно что угодно. Но до недавнего времени разработка скриптов на Clojure была довольно трудной задачей, в основном, из-за медленного старта JVM.

Появление GraalVM позволило обойти это ограничение. Скрипты, написанные на Clojure, стартуют практически мгновенно. При этом в процессе разработки доступен REPL и весь арсенал языка Clojure. На бесплатном вебинаре вы познакомитесь с проектом Babashka и узнаете, как именно эта библиотека помогает разрабатывать скрипты. Приглашаем всех желающих.

Записаться на открытый урок можно на странице "Clojure Developer".

Комментарии (5)


  1. danslapman
    00.00.0000 00:00

    Казалось бы можно было бы просто попросить ChatGPT перевести статью с использованием идеоматичной терминологии...


    1. aelaa
      00.00.0000 00:00

      И перевод, возможно, вышел бы лучше

      Используйте reduce для их уменьшения

      По крайней мере такое, возможно, не всплыло бы


    1. newpy
      00.00.0000 00:00
      -1

      Установки

      у заголовка не было контекста, и поэтому google translate не справился. Не льстите автору, если он человек, он GPT не использовал.


  1. Vest
    00.00.0000 00:00
    +1

    Вот еще один ресурс. Там и переводить много не придётся: https://learnxinyminutes.com/docs/clojure/


  1. leshabirukov
    00.00.0000 00:00

    Это Delphi — для ежа,
    Это Swift он для стрижа,
    Это Python — для утят,
    Это Scratch он для котят,
    Это Haskell — для бобра,
    А для волка — Clojure.