Clojure #2
Ugly way for binding coll 
Удобно ли так писать? 
(let [a (nth coll 0) 
b (nth coll 1) 
c (nth coll 2)] 
(doSmth ...))
Destructuring! 
Что-то очень похожее на pattern matching: 
(let [[a b c] coll] 
(doSmth ...))
tail 
Мы уже видели пример ранее: 
(let [[a b & tail] coll] 
(doSmth ...))
maps destructuring 
Для maps это тоже возможно: 
(def my-map {"Clojure" :lang}) 
(let [{lang :lang} my-map] 
(doSmth ...))
Strings 
Destructuring доступен и для строк: 
(defn foo [[a & tail]] 
(= (clojure.string/upper-case a) 
(str a)))
:as keyword 
Иногда саму коллекцию тоже хочется иметь 
в арсенале, тогда на помощь приходит :as 
(defn [[head & tail :as coll]] 
(doSomth ...))
destructuring everywhere 
defn, fn и let - это там, где это доступно. 
Но из-за того, что многое приводится 
макросами к этим конструкциям, 
destructuring доступен очень много где.
Java compatibility
Calling methods 
Это крайне просто: 
(.methodName object) 
(.toLowerCase "AbC") 
; "abc"
Methods with args 
(.methodName object args) 
(.equals "a" "b")
java.lang.Class 
Символ ссылающийся на имя класса также 
дает нам и сам класс (compare .class, classOf) 
(map #(.getName %) 
(.getMethods Boolean))
static things 
Static field: 
(Math/PI) 
Static method: 
(ClassName/methodName args) 
(Thread/activeCount) ; 1
Getting all together 
Наконец-то классический HelloWorld! 
(.println (System/out) 
"Hello, World!")
Operator . 
Есть и альтернатива всему этому… 
(.toUpperCase "abc") 
(. "abc" toUpperCase) 
(Thread/activeCount) 
(. Thread activeCount) 
(.equals "a" "b") 
(. "a" equals "b")
Operator .. 
Последовательные вызовы можно 
объединить: 
(. (. System out) println 
"Hello, World!") 
(.. System out 
(println "Hello, World!"))
operator new 
Есть старый добрый оператор: 
(new ClassName args) 
(new Object) 
(new String "text") 
(new java.util.HashMap)
dot syntax 
Но есть и альтернатива… 
(ClassName. args) 
(Object.) 
(String. "text") 
(java.util.HashMap.)
for example 
(let [h (java.util.HashMap.)] 
(.put h "a" "b") 
(.get h "a"))
import 
Есть и возможность импортировать имена: 
(import [java.util HashMap 
ArrayList]) 
(HashMap.) 
(ArrayList.)
operator doto 
Что делает этот оператор? 
(doto (HashMap.) 
(.put 1 "one") 
(.put 2 "two"))
instance? 
Аналог instanceof/isInstanceOf 
(instance? HashSet (HashSet.))
Java classes in Clojure
reify 
С помощью этого оператора можно 
создавать анонимные классы: 
(reify 
InterfaceName 
(methodName [this args] 
body))
reify 
А также для нескольких интерфейсов: 
(reify 
InterfaceName 
(methodName [this args] 
body) 
AnotherInterface 
(method-2 [this args] body-2))
Runnable 
(def runnable 
(reify Runnable 
(run [this] 
(println "text")))) 
(.start (Thread. runnable))
Array compatibility 
(make-array (Integer/TYPE) 4) 
(make-array String 3) 
(to-array [1 2 3]) 
;untyped: java.lang.Object[] 
(into-array [1 2 3]) 
;typed: java.lang.Long[]
Declaring class in Clojure
gen-class 
Класс объявить легко: 
(gen-class 
:name my.class.Name) 
(compile 'my.class) 
(my.className.)
:methods 
Объявить: 
:methods [[methodName 
[String] String]] 
И реализовать: 
(defn -methodName 
[arg] (str "x " arg))
:prefix 
Можно добавить префикс для понятности: 
:prefix prefix 
Тогда реализовать: 
(defn prefix-methodName 
[arg] (str "x " arg))
:init/:constructors 
Объявить имя для конструктора, и 
сигнатуры конструкторов: 
:init init 
:constructors [[String] [] 
[String String] [String]]
:state 
При создании класса можно записать 
некоторый state, который можно вернуть 
как [[super-call] state] из конструктора. 
Вызывать можно так: 
:state state 
(.state this)
:implements/:extends 
Указать супер класс: 
:extends ClassName 
Можно также объявить список интерфейсов: 
:implements [java.io.Serializable]
:gen-class 
Желательно все это использовать в макросе 
ns, где и указать все, что требуется: 
(ns my.namespace 
(:gen-class))
Polymorphism
Example 
(def circle {:type ::circle :r 10}) 
(def rect {:type ::rectangle :w 10 :h 20}) 
(defn area [{type :type :as shape}] 
(cond 
(= type ::circle) 
(* Math/PI (:r shape) (:r shape)) 
(= type ::rectangle) 
(* (:w shape) (:h shape))))
solution: multimethods 
● Runtime polymorphism 
● Позволяет dispatching по совершенно 
любому объекту
defmulti 
Объявление очень простое: 
(defmulti function-name 
"Doc string" 
dispatch-fn)
defmethod 
За объявлением следуют реализации: 
(defmethod fName1 
match-1 
[this] ...) 
(defmethod fName2 
match-2 
[this] ...)
our example 
(defmulti area :type) 
(defmethod area ::circle [circle] 
(* Math/PI (:r circle) (:r circle))) 
(defmethod area ::rectangle [rect] 
(* (:w rect) (:h rect)))
juxt 
Может возникнуть вопрос, как диспатчится 
по нескольким параметрам. В этом нам 
помогает метод juxt: 
((juxt + - * /) 1 2) 
; [3 -1 2 1/2]
triangle 
Тем самым можем написать следующее: 
(defmulti triangle-type (juxt :type unique-sides)) 
(defmethod triangle-type [::triangle 1] [_] 
:equilateral) 
(defmethod triangle-type [::triangle 2] [_] 
:isosceles) 
(defmethod triangle-type [::triangle 3] [_] 
:scalene)
derive 
Иногда не хочеться писать несколько 
реализаций одного и того же, тогда можно 
использовать derive: 
(derive ::square ::rectangle) 
Теперь можно не писать реализацию area 
для ::square.
:default 
Также можно определить, что делать если 
получили что-то не то (а не просто кидать 
исключение): 
(defmethod triangle-type :default [_] 
:unknown)
factorial homework 
Давайте напишем factorial, используя 
multimethods.
Protocols
defprotocol 
По сути объявляем новый интерфейс (в 
реальности подобный интерфейс и правда 
появляется): 
(defprotocol P 
"doc string" 
(method1 [a] "doc") 
(method2 [a] [a b] "doc2")
Shape 
(defprotocol Shape 
(area [shape]) 
(perimeter [shape])
extend 
(extend Shape 
Rectangle 
{:area (fn [shape] 
(* (:w shape) (:h shape)))} 
{:perimeter (fn [shape] 
(* 2 (+ (:w shape) 
(:h shape))))})
extend-protocol 
(extend-protocol Shape 
Rectangle 
(area [rect] ...) 
(perimeter [rect] ...) 
Circle 
(area [rect] ...) 
(perimeter [rect] ...))
extend-type 
Можно расширять несколько протоколов: 
(extend-type Rectangle 
Shape 
(area [rect] ...) 
(perimeter [rect] ...) 
Drawable 
(draw [rect] ...))
reify 
Аналогично анонимным классам, 
анонимные протоколы: 
(reify Shape 
(area [rectangle] ...) 
(perimeter [rectangle] ...)
defrecord 
Это уже некая реализация в виде класса: 
(defrecord Parallelogram [h b] 
Shape 
(area [p] (* (.b p) (.h p))) 
(perimeter [p] ...))
Concurrency
Starting a thread 
Это очень просто, делается также, как и в 
Java: 
(.start 
(Thread. (fn [] ...))
ExecutorPool 
(let [pool (Executors/newFixedThreadPool 16) 
callable (cast Callable (fn [] 
(reduce + (range 0 10000)))) 
task (.submit pool callable)] 
(.get task))
atoms 
Полезны, чтобы управлять shared memory 
синхронно. 
(def a (atom {}))
deref 
Вычисляет значение атома: 
(deref my-atom) 
Есть и альтернативный синтаксис, более 
короткий: 
@my-atom
swap! 
Все изменения атомов должны быть без 
side-effects, потому что они могут быть 
вычислены несколько раз. Заменяет atom на 
применение функции к аргументам. 
(swap! my-atom fn args)
reset! 
Устанавливает новое значение атома: 
(reset! my-atom new-value)
refs 
Синхронная память, управляемая через 
транзакции: 
(ref [])
dosync 
Этот метод запускает транзакцию. 
Транзакция будет либо целиком выполнена, 
либо целиком невыполнена. Может 
вычисляться несколько раз. 
(let [my-ref (ref false)] 
(dosync (ref-set ref true))
ref-set 
Просто устанавливает значение в 
переменной
alter 
Действует, почти как swap!. 
(alter! my-ref fun args) 
(apply fun my-ref-value args)
ensure 
Гарантирует, что переменная не будет 
изменена в другой транзакции.
commute 
Исполняет функцию над значением ссылки 
и присваивает ссылке. 
Исполняется более конкурентно, чем alter, 
поэтому требует от функции быть 
коммутативной.
agent 
Позволяет стартовать агента, который будет 
исполнять задания асинхронно.
send 
Позволяет отправить агенту задание на 
изменение значения. 
(def my-agent (agent 100)) 
(send my-agent + 100) 
@my-agent 
; 200 
Агенты также интегрированы с STM. send в 
транзакции действует как и alter.
future 
Похоже на агента, только исполняется с 
помощью java.util.concrrent.Future 
(def ft (future 
(reduce + (range 0 10000)))) 
(realized? ft) ;true 
@ft ;49995000
locking 
Позволяет удержать lock на каком-нибудь 
объекте: 
(let [a (java.util.ArrayList.)] 
(locking a 
(.add a 10))) 
Помогает в тех случаях, когда нужна 
совместимость с Java кодом.
bank account homework 
Давайте напишем transfer денег с одного 
банковского аккаунта на другой.
Macros
macros 
Позволяет исполнить код на этапе 
компиляции. Макро-функции могут 
изменить дерево программы, тем самым, мы 
можем изменять синтаксис языка. 
(defmacro call [sym & params] 
(cons sym params)) 
(call + 1 2 3)
unquote 
Можно новое дерево не составлять вручную, 
как в предыдущем примере, а собрать с 
помощью unquote: 
(defmacro twice 
[form] 
`(do 
~form 
~form))
splice-unquote 
Можно список разобрать поэлементно: 
(defmacro my-macro 
[sym forms] 
`(~sym ~@forms))
gensym 
Иногда в макросе хотим создать новую 
переменную, но не всегда мы хотим дать ей 
разумное имя, которое может пересекаться с 
чем-то еще: 
(defmacro f 
[t] 
`(let [z# ~t]))
unquote symbol 
Можем задать новый символ, который будет 
виден магическим образом в коде 
(defmacro with-a 
[& body] 
`(let [~'a 2] 
~@body)) 
(with-a (println a))
macroexpand 
Метод, с которым можно дебажить макросы. 
(defmacro with-a 
[& body] 
`(println ~body)) 
(macroexpand '(with-a 123)) 
; (clojure.core/println (123))
macroexpand-1 
Предыдущий метод повторяет этот, пока это 
имеет смысл, а этот метод раскрывает 
макрос той формы, которую ему передали, 
но не раскрывает подформы. 
Для этого есть macroexpand-all.
or homework 
Давайте напишем свою реализацию or: 
(defmacro my-or 
([] nil) 
([x] x) 
([x & next] 
...))
let homework 
А теперь let, через анонимную функцию: 
(defmacro my-let 
[bindings & body] 
... 
)