clojure #2 (2014)

82
Clojure #2

Upload: alexander-podkhalyuzin

Post on 14-Jul-2015

186 views

Category:

Engineering


1 download

TRANSCRIPT

Clojure #2

Удобно ли так писать?(let [a (nth coll 0)

b (nth coll 1)

c (nth coll 2)]

(doSmth ...))

Ugly way for binding coll

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

Это крайне просто:(.methodName object)

(.toLowerCase "AbC")

; "abc"

Calling methods

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

InterfaceName

(methodName [this args]

body))

reify

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

:name my.class.Name)

(compile 'my.class)

(my.className.)

gen-class

: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 P

"doc string"

(method1 [a] "doc")

(method2 [a] [a b] "doc2")

defprotocol

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

Это очень просто, делается также, как и в Java:(.start

(Thread. (fn [] ...))

Starting a thread

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

Позволяет исполнить код на этапе компиляции. Макро-функции могут изменить дерево программы, тем самым, мы можем изменять синтаксис языка.(defmacro call [sym & params]

(cons sym params))

(call + 1 2 3)

macros

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]

...

)