Краткий экскурс в синтаксис 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».
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/725060/
Добавить комментарий