Почему 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)

Мой вопрос: Почему Clojure let и for обе монады?

Ответ 1

Почему 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 похож на монодальную идентичность, но в рамках выраженного sugared выражения.

Но это не все монады.

Структура/контракт Monads могут использоваться другими способами. Многие полезные монадические функции могут быть определены только в терминах 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 не полностью раскрывают эту общую структуру и поэтому не могут в полной мере участвовать (в общем виде). ​​

Ответ 2

Я бы сказал, что более корректно называть let и for do-notation для моноданных и монады списка соответственно - они сами не являются монадами, поскольку они являются битами синтаксиса, а не структурами данных со связанными функциями.

Причина, по которой монад возникает вообще, заключается в том, что for - хорошая нотация, которая использует монадическое поведение списков (или в clojure, последовательностях), чтобы легко писать код, который делает некоторые полезные вещи.

Ответ 3

let является тождественной монадой. Специальной техники нет, каждое связывание просто доступно для последующих этапов расчета.

for - это монада списка/последовательности с охранниками, плюс немного чего-то дополнительного. Здесь каждый шаг в вычислении, как ожидается, создает последовательность; техника монады заботится о конкатенации последовательностей. Охранники могут быть введены с помощью :when, а :let вводит промежуточные вспомогательные привязки (как let делает в Haskell). "Что-то дополнительное" приходит в виде :while.