In this discussion Brian Marick指出let
和for
是Clojure中的monad:
那就是说,真正的通用monad倾向于以特殊形式写入语言。 Clojure的
let
和for
都是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的let
和for
都是monad?
答案 0 :(得分:35)
为什么Clojure的let
和for
都是monad?
他们不是。
Clojure的let
和for
不是monad,因为它们没有完全暴露他们的Monadic共同结构。他们更像是一个含糖监狱中的单子。
什么是monads?
在Clojure的说法中,monad可以被描述为Monad协议的具体化,Monad协议的功能预期彼此之间以及在某些明确定义的方式上的具体类型。这并不是说monad必须与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])))
糖
使用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函数只能用bind
和unit
来定义,例如
(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的let
和for
没有完全暴露这种常见结构,因此无法完全参与(以通用方式)。
答案 1 :(得分:9)
我会说分别为身份monad和list monad调用let
和for
do-notation更为正确 - 它们本身不是monad,因为它们是语法的一部分而不是具有相关功能的数据结构。
monad出现的原因是for
是一个很好的符号,它利用列表的monadic行为(或者在clojure,序列中)来轻松编写执行一些有用的东西的代码。
答案 2 :(得分:4)
let
是身份monad。没有特殊的机器,每个绑定都可以用于计算的后续阶段。
for
是带有警卫的列表/序列monad,加上一些额外的东西。这里计算中的每个步骤都会产生一个序列; monad的机器负责连接序列。可以使用:when
引入防护,而:let
引入中间帮助程序绑定(如Haskell中的let
)。 “额外的东西”以:while
的形式出现。