Как тестировать?
(defn new-article [title body]
{:id (java.util.UUID/randomUUID)
:created-at (java.time.Instant/now)
:title title
:body body})
(new-article "Lorem ipsum" "dolor sit amet...")
Жадная чистая функция
(defn new-article [id created-at title body]
{:id id
:created-at created-at
:title title
:body body})
(let [new-article* (partial new-article
(java.util.UUID/randomUUID)
(java.time.Instant/now))]
(new-article* "Lorem ipsum" "dolor sit amet..."))
Ненужное значение
(defn new-article [id created-at value title body]
{:id id
:created-at created-at
:title title
:body body
:answer (if (= "The Hitchhiker's Guide..." title)
value
0)})
ООП
(defn new-article-factory [get-id get-instant]
(fn [title body]
{:id (get-id)
:created-at (get-instant)
:title title
:body body}))
(let [get-id #(java.util.UUID/randomUUID)
get-instant #(java.time.Instant/now)
new-article (new-article-factory get-id get-instant)]
(new-article "Lorem ipsum" "dolor sit amet..."))
Сложно тестировать порядок вызовов
(let [log (atom [])
get-id #(do
(swap! log conj :get-id)
(java.util.UUID/randomUUID))
get-instant #(do
(swap! log conj :get-instant)
(java.time.Instant/now))
new-article (new-article-factory get-id get-instant)]
(new-article "Lorem ipsum" "dolor sit amet...")
(assert (= [:get-id :get-instant] @log)))
Контекст
(declare ^:dynamic *get-id*
^:dynamic *get-instant*)
(defn new-article [title body]
{:id (*get-id*)
:created-at (*get-instant*)
:title title
:body body})
(binding [*get-id* #(java.util.UUID/randomUUID)
*get-instant* #(java.time.Instant/now)]
(new-article "Lorem ipsum" "dolor sit amet..."))
Эффект и продолжение
(defn new-article [title body]
[:get-id (fn [id]
[:get-instant (fn [created-at]
{:id id
:created-at created-at
:title title
:body body})])])
(let [[ef-1 cont-1] (new-article "Lorem ipsum"
"dolor sit amet...")
_ (assert (= :get-id ef-1))
[ef-2 cont-2] (cont-1 (java.util.UUID/randomUUID))
_ (assert (= :get-instant ef-2))]
(cont-2 (java.time.Instant/now)))
darkleaf/effect
(defn example-fn [x]
(with-effects
(let [rnd (! (effect [:random]))]
(- (* 2. x rnd) x))))
(def continuation (e/continuation example-fn))
Макрос with-effects
Функция e/continuation
Функция !
;; эффект
(! (effect [:some-effect :arg])
(! (effect `any-value-with-metadata-support))
;; вызов функции с эффектами
(! (some-fn! :val))
;; простое значения
(! 42)
(! [:some-value])
;; вызов обычных функций
(! (inc 41))
;; эффект как значение
(let [my-effect (effect [:some-effect])]
(! my-effect)
real example
(defn user-registration []
(with-effects
(if (-> (! (effect [:session/get]))
:current-user-id some?)
(! (effect [:ui.screen/show :main]))
(loop [user (make-user)]
(let [user (! (effect [:ui.form/edit user]))
user (validate form)]
(if (has-errors? user)
(recur user)
(do
;; ...
(! (effect [:persistence/create user])))))))))
re-frame
(reg-event-fx
:my-event
(fn [cofx [_ a]]
{:db (assoc (:db cofx) :flag a)
:dispatch [:do-something-else 3]}))
Interpretator
(defn example-fn [x]
(with-effects
(let [rnd (! effect [:random]))]
(- (* 2. x rnd) x))))
(defn effect-!>coeffect [effect]
(match effect [:random] 0.75))
(def continuation (e/continuation example-fn))
(defn example-fn* [x]
(e/perform effect-!>coeffect continuation [x]))
(assert (= 0.5 (example-fn* 1)))
Script
(defn example-fn [x]
(with-effects
(let [rnd (! effect [:random]))]
(- (* 2. x rnd) x))))
(deftest example-fn-test
(let [continuation (e/continuation example-fn)
script [{:args [1]}
{:effect [:random]
:coeffect 0.75}
{:return 0.5}]]
(script/test continuation script))))))
core-analogs
reduce!
mapv!
->!
->>!
Sync & Async
(defn effect-!>coeffect [effect]
(match effect
[:random] 0.75))
(defn f [x]
(e/perform effect-!>coeffect continuation [x]))
(defn effect-!>coeffect [effect respond raise]
(match effect
[:random] (next-tick respond 0.75)))
(defn f [x respond raise]
(e/perform effect-!>coeffect
continuation
[x]
respond raise))
Middleware
(defn wrap-blank [continuation]
(when (some? continuation)
(fn [coeffect]
(let [[effect continuation] (continuation coeffect)]
[effect (wrap-blank continuation)]))))
(def continuation (-> (e/continuation example-fn)
(wrap-blank)))
darkleaf.effect.middleware.context
(defn state-example []
(with-effects
[(! (effect [:update inc]))
(! (effect [:update + 2]))
(! (effect [:get]))]))
(defn effect-!>coeffect [[context effect]]
(match effect
[:get]
[context (:state context)]
[:update f & args]
(let [context (apply update context :state f args)]
[context (:state context)])))
darkleaf.effect.middleware.reduced
(defn maybe-example [x]
(with-effects
(+ 5 (! (effect [:maybe x])))))
(defn effect->coeffect [effect]
(match effect
[:maybe nil] (reduced nil)
[:maybe val] val))
darkleaf.effect.middleware.log
(defn suspend-resume-example [x]
(with-effects
(let [a (! (effect [:suspend]))]
...)))
(defn effect-!>coeffect [effect]
(match effect
[:effect] :coeffect
[:suspend] ::log/suspend))
(def continuation (-> (e/continuation suspend-resume-example)
(log/wrap-log)))
(assert (= [::log/suspended [{:coeffect [:arg]
:next-effect [:suspend]}]]
(e/perform effect-!>coeffect continuation [:arg])
(def continuation (-> (e/continuation ef)
(log/wrap-log)
(log/resume log))