clojure #2 (2014)
TRANSCRIPT
Удобно ли так писать?(let [a (nth coll 0)
b (nth coll 1)
c (nth coll 2)]
(doSmth ...))
Ugly way for binding coll
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.lang.Class
Символ ссылающийся на имя класса также дает нам и сам класс (compare .class, classOf)(map #(.getName %)
(.getMethods Boolean))
static things
Static field:(Math/PI)
Static method:(ClassName/methodName args)
(Thread/activeCount) ; 1
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)
import
Есть и возможность импортировать имена:(import [java.util HashMap
ArrayList])
(HashMap.)
(ArrayList.)
С помощью этого оператора можно создавать анонимные классы:(reify
InterfaceName
(methodName [this args]
body))
reify
reify
А также для нескольких интерфейсов:(reify
InterfaceName
(methodName [this args]
body)
AnotherInterface
(method-2 [this args] body-2))
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[]
: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))
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))))
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)
По сути объявляем новый интерфейс (в реальности подобный интерфейс и правда появляется):(defprotocol P
"doc string"
(method1 [a] "doc")
(method2 [a] [a b] "doc2")
defprotocol
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] ...))
ExecutorPool(let [pool (Executors/newFixedThreadPool 16)
callable (cast Callable (fn []
(reduce + (range 0 10000))))
task (.submit pool callable)]
(.get task))
deref
Вычисляет значение атома:(deref my-atom)
Есть и альтернативный синтаксис, более короткий:@my-atom
swap!
Все изменения атомов должны быть без side-effects, потому что они могут быть вычислены несколько раз. Заменяет atom на применение функции к аргументам.(swap! my-atom fn args)
dosync
Этот метод запускает транзакцию. Транзакция будет либо целиком выполнена, либо целиком невыполнена. Может вычисляться несколько раз.(let [my-ref (ref false)]
(dosync (ref-set ref true))
commute
Исполняет функцию над значением ссылки и присваивает ссылке.Исполняется более конкурентно, чем alter, поэтому требует от функции быть коммутативной.
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 кодом.
Позволяет исполнить код на этапе компиляции. Макро-функции могут изменить дерево программы, тем самым, мы можем изменять синтаксис языка.(defmacro call [sym & params]
(cons sym params))
(call + 1 2 3)
macros
unquote
Можно новое дерево не составлять вручную, как в предыдущем примере, а собрать с помощью unquote:(defmacro twice
[form]
`(do
~form
~form))
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.