Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Пишу сейчас процессинг. Море операций с файловой системой, базой данных, сторонними сервисами и программами. Пришел к похожей схеме с различием в структуре данных.
Для себя остановился на funcool/cats и монаде Either.
PS: Ваш блок let функции buy-lot заставляет моё сердце кровоточить :)
Многословность. В моем понимании в let блок должен быть максимально лаконичным.
with-lot-fn, buy-lot-fn вынести наружу. buy-lot-fn разбить для более легкого понимания.
Лично меня начинает клинить если в функции большая вложенность.
Вложенные let блоки причиняют страдания.
Всё это разумеется ИМХО
(defn get-lock [{:keys [value_a ...] :as context}]
(util/with-result-or-error
#(lock-service/create-lock value_a ... )
:lock
context))
lock-service/create-lock, то значит лок не был создан и в context ничего сохранять не нужно, а если lock-service/create-lock вернул успешный результат, то он сохранится в контекст (первый же тест это покажет и если он успешный, то сохранение лока в контекст всегда будет успешным).Сравнить контекстное программирование с программированием с монадами — действительно интересный и важный вопрос.Вы так пишете, будто «контекстное программирование» — это прямо какая-то отдельная новая парадигма :)
Можете дать ссылку на библиотеку с реализацией монады StateT Either / State+Either / what everДа без проблем :)
(use
'[monads.core]
'[monads.error :as e]
'[monads.types :only [either]])
(defn maybe-inc [x]
(println "call" `(maybe-inc ~x))
(if (> x 10)
(fail "Too big")
(return (inc x))))
(defn errorm->vec [m]
(either (partial vector :fail) (partial vector :ok) m))
(defn until-first-error [fs init]
(run-monad e/m (reduce >>= (return init) fs)))
(defn with-result-on-error [f key ctx]
(>>= (f) #(return (assoc ctx key %))))
(errorm->vec
(until-first-error
(repeat 5 maybe-inc)
9))
(errorm->vec
(until-first-error
[(partial with-result-on-error #(return 0) :x)
(partial with-result-on-error #(return 1) :y)
(partial with-result-on-error #(return 2) :z)]
{}))
do-something-elementary(val) -> updated_val | (throw e)(defn with-result-or-error [f k c]
(assoc c k (f))-> и try/catch на верхнем уровне.-> + try/catch нам не подходит.until-first-error функцию, которая возвращает последнее состояние контекста до появления ошибки:until-first-error(fs, init_context) -> [:ok updated_context] | [:error [reason last_context]]until-first-error в цикл по количеству повторов:;; [:ok updated_ctx] | [:error [last_reason last_ctx]]
(defn with-retry [ctx_fn max_attempts ctx & [ on-result-fn ]]
(loop [attempt 1
last_reason nil
ctx ctx]
(if (> attempt max_attempts)
[:error [last_reason ctx]]
(match (ctx_fn ctx on-result-fn)
[:ok updated_ctx]
[:ok updated_ctx]
[:error [reason updated_ctx]]
(recur (inc attempt) reason updated_ctx)))))
(with-retry (partial util/until-first-error fs) 10 {})(use 'clojure.algo.monads)
(defmonad either-m
[m-bind (fn [[z v :as m] f] (if (= z :right) (f v) m))
m-result (fn [v] [:right v])])
(defn maybe-inc [x]
(println "call" `(maybe-inc ~x))
(if (> x 9)
[:left "Too big"]
[:right (inc x)]))
(with-monad either-m
((m-chain ;; склеивает функции при помощи m-bind
(repeat 10 maybe-inc))
0))(defn e-bind-left [[z v :as m] f] (if (= z :left) (f v) m)(with-monad either-m
(-> [:right 10] ;; init
(m-bind (m-chain (repeat 5 maybe-inc)))
(e-bind-left #(do (println "Handle error" %) [:right 0]))
(m-bind (m-chain (repeat 5 maybe-inc)))))
(defn e-with-retry [f]
(if (<= attempts 0)
(fn [v] (e-bind-left (f v)
(fn [e] [:left [e v]])))
(fn [v] (e-bind-left (f v)
(fn [_] ((e-with-retry f (dec n)) v))))))(defn e-with-retry [f n]
(if (<= n 0)
(fn [v] (e-bind-left (f v)
(fn [e] [:left [e v]])))
(fn [v] (e-bind-left (f v)
(fn [_] ((e-with-retry f (dec n)) v))))))
О декомпозии кода замолвим слово: контекстное программирование