Почему Clojure'let` и `for` обе монады?
В этой дискуссии Брайан Марик подчеркивает, что let
а такжеfor
монады в Clojure:
Тем не менее, монадыдействительно общего назначения, как правило, записываются на языке как специальные формы. Clojure-х
let
а такжеfor
Обе монады, но вам не нужно знать, что их использовать.
Это let
user=> (let [c (+ 1 2)
[d e] [5 6]]
(-> (+ d e) (- c)))
8
Этоfor
user=> (for [x [0 1 2 3 4 5]
:let [y (* x 3)]
:when (even? y)]
y)
(0 6 12)
Мой вопрос:почему Clojurelet
а такжеfor
обе монады?
3 ответа
Почему Clojure let
а также for
обе монады?
Это не так.
Clojure-х let
а также for
не монады, потому что они не полностью раскрывают свою монадическую общую структуру. Они больше похожи на монады в сладкой тюрьме.
Какие монады?
На языке Clojure, монада может быть описана как повторение протокола Monad, функции которого, как ожидается, будут вести себя друг с другом и с типом reified определенными четко определенными способами. Это не означает, что монады должны быть реализованы с defprotocol
, reify
и друзей, но это дает идею, не говоря уже о классах типов или категориях.
(defprotocol Monad
(bind [_ mv f])
(unit [_ v]))
(def id-monad
(reify Monad
(bind [_ mv f] (f mv))
(unit [_ v] v)))
(def seq-monad
(reify Monad
(bind [_ mv f] (mapcat f mv))
(unit [_ v] [v])))
сахар
Монады могут быть грязными в использовании
(bind seq-monad (range 6) (fn [a]
(bind seq-monad (range a) (fn [b]
(unit seq-monad (* a b))))))
;=> (0 0 2 0 3 6 0 4 8 12 0 5 10 15 20)
Без сахара
(defn do-monad-comp
[monad body return]
(reduce
(fn [a [exp sym]] (list 'bind monad exp (list 'fn [sym] a)))
(list 'unit monad return)
(partition 2 (rseq body))))
(defmacro do-monad [monad body return]
(do-monad-comp monad body return))
Это легче написать
(do-monad seq-monad
[a (range 6)
b (range a)]
(* a b))
;=> (0 0 2 0 3 6 0 4 8 12 0 5 10 15 20)
Но разве это не просто...?
Это выглядит как
(for
[a (range 6)
b (range a)]
(* a b))
;=> (0 0 2 0 3 6 0 4 8 12 0 5 10 15 20)
А также
(do-monad id-monad
[a 6
b (inc a)]
(* a b))
;=> 42
Выглядит очень похоже
(let
[a 6
b (inc a)]
(* a b))
;=> 42
Так да, for
это как последовательность монада и let
походит на монаду идентичности, но в границах подслащенного выражения.
Но это еще не все монады.
Структура / контракт монад может быть использована другими способами. Многие полезные монадические функции могут быть определены только в терминах bind
а также unit
, например
(defn fmap
[monad f mv]
(bind monad mv (fn [v] (unit monad (f v)))))
Чтобы их можно было использовать с любой монадой
(fmap id-monad inc 1)
;=> 2
(fmap seq-monad inc [1 2 3 4])
;=> (2 3 4 5)
Это может быть довольно тривиальным примером, но в более общем / мощном смысле монады могут быть составлены, преобразованы и т. Д. Единообразным образом из-за их общей структуры. Clojure-х let
а также for
не полностью раскрыть эту общую структуру, и поэтому не может в полной мере участвовать (в общем).
Я бы сказал, что правильнее звонить let
а также for
do-notation для монады идентификатора и монады списка, соответственно - они сами не монады, так как они являются битами синтаксиса, а не структурами данных со связанными функциями.
Причина, по которой монады появляются вообще, заключается в том, что for
хорошая нотация, которая использует монадическое поведение списков (или, в более позднем случае, последовательностей), чтобы легко написать код, который делает некоторые полезные вещи.
let
это монада личности Специальных механизмов нет, каждая привязка просто делается доступной для последующих этапов вычисления.
for
это список / последовательность монада с охраной, плюс немного чего-то дополнительного. Здесь каждый шаг в вычислении должен производить последовательность; механизм монады заботится о последовательности последовательностей. Охранники могут быть введены с :when
, в то время как :let
вводит промежуточные вспомогательные привязки (как let
делает в Хаскеле). "Что-то дополнительное" приходит в форме :while
,