为什么Clojure的'let`和`for`都是monad?

时间:2014-02-13 11:00:04

标签: clojure monads let

In this discussion Brian Marick指出letfor是Clojure中的monad:

  

那就是说,真正的通用monad倾向于以特殊形式写入语言。 Clojure的letfor都是monad,但你不需要知道使用它们。

这是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的letfor都是monad?

3 个答案:

答案 0 :(得分:35)

为什么Clojure的letfor都是monad?

他们不是。

Clojure的letfor不是monad,因为它们没有完全暴露他们的Monadic共同结构。他们更像是一个含糖监狱中的单子。

什么是monads?

在Clojure的说法中,monad可以被描述为Monad协议的具体化,Monad协议的功能预期彼此之间以及在某些明确定义的方式上的具体类型。这并不是说monad必须与defprotocolreify和朋友一起实现,但这样就无需谈论类型类别或类别就可以实现这一点。

(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])))

使用Monads可能很麻烦

(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 喜欢序列monad,let 喜欢身份monad,但是在限制中含糖表达。

但并非所有monad都是。

Monads的结构/合约​​可以通过其他方式加以利用。许多有用的monadic函数只能用bindunit来定义,例如

(defn fmap 
  [monad f mv] 
  (bind monad mv (fn [v] (unit monad (f v)))))

这样他们就可以和任何monad一起使用

(fmap id-monad inc 1)
;=> 2

(fmap seq-monad inc [1 2 3 4])
;=> (2 3 4 5)

这可能是一个相当简单的例子,但由于它们的共同结构,更通用/有力的monad可以以统一的方式组合,转换等。 Clojure的letfor没有完全暴露这种常见结构,因此无法完全参与(以通用方式)。

答案 1 :(得分:9)

我会说分别为身份monad和list monad调用letfor do-notation更为正确 - 它们本身不是monad,因为它们是语法的一部分而不是具有相关功能的数据结构。

monad出现的原因是for是一个很好的符号,它利用列表的monadic行为(或者在clojure,序列中)来轻松编写执行一些有用的东西的代码。

答案 2 :(得分:4)

let是身份monad。没有特殊的机器,每个绑定都可以用于计算的后续阶段。

for是带有警卫的列表/序列monad,加上一些额外的东西。这里计算中的每个步骤都会产生一个序列; monad的机器负责连接序列。可以使用:when引入防护,而:let引入中间帮助程序绑定(如Haskell中的let)。 “额外的东西”以:while的形式出现。