Реализация монады Either.
Моделирует вычисления, которые могут закончитьсь неудачей.
(ns example.app
(:require
;;...
[darkleaf.either :as e]))
;;...
(defn- check-logged-out= []
(if (user-session/logged-out?)
(e/right)
(e/left [::already-logged-in])))
(defn- check-params= [params]
(if-let [exp (s/explain-data ::params params)]
(e/left [::invalid-params exp])
(e/right)))
(defn- check-not-registered= [params]
(if (user-q/get-by-login (:login params))
(e/left [::already-registered])
(e/right)))
(defn- create-user [params]
(storage/tx-create (user/build params)))
(defn process [params]
(e/extract
(e/let= [ok (check-logged-out=)
ok (check-params= params)
ok (check-not-registered= params)
user (create-user params)]
(user-session/log-in! user)
[::processed user])))right и left - конструкторы соответствующих типов.
Могут принимать один аргумент или не принимать аргументов вовсе.
Если вызваны без агрументов, то оборачивают nil.
extract - извлекает значение
Макрос let= реализует всю монадическую "магию".
Работает он следующим образом. Допустим, есть выражение:
(let= [val (fn-1)]
(fn-2 val)
(fn-3 val))- Если
fn-1вернула(left 1), то результатом всего выражения будет(left 1) - Если
fn-1вернула(right 1), тоvalсвязывается с1 - Eсли
fn-1вернула1, тоvalсвязывается с1 - Значение выражения
(fn-2 val)всегда игнорируется, по аналогии с обычнымlet - Если
fn-3вернула(left 3), то результатом всего выражения будет(left 3) - Если
fn-3вернула(right 3), то результатом всего выражения будет(right 3) - Если
fn-3вернула3, то результатом всего выражения будет(right 3)
Предикаты left? и right? тестируют значение на соотвутствующий тип.
invert меняет обертку на противоположную. Например, (invert (left 1)) возвращает (right 1).
bimap, map-left, map-right - применяют функцию к содержимому контейнера.
>>= позволяет строить цепочки вызовов. Результат функции предается в другую функцию.
Например (>>= (fn-1) fn-2 fn-3).
Правила аналогичны let=, и данную цепочку можно записать как
(let [v (fn-1)
v (fn-2 v)
v (fn-3 v)]
v)Макрос >> строит цепочки из значений: (>> (fn-1) (fn-2) (fn-3)). Подобен and для булевых значений.
Все случаи использования смотрите в тестах.
deps.edn:
{:deps {darkleaf/either
{:git/url "https://github.com/darkleaf/either.git"
:sha "b84fedd52afef27f938531a4836202512ae987e2"}}}