在学习Clojure的同时,我花了很多时间试图理解monad - 它们是什么以及我们如何使用它们......并没有太多的成功。然而,我发现了一个出色的“Monads for Dummies”视频系列 - http://vimeo.com/20717301 - 由Brian Marik为Clojure发布
到目前为止,我对monad的理解是它有点像一个宏,因为它允许一组语句以易于阅读的形式编写 - 但是monad更加正式化。我的观察仅限于两个例子:
1。 Identity Monad(或'let'monad)取自http://onclojure.com/2009/03/05/a-monad-tutorial-for-clojure-programmers-part-1/
我们希望撰写的表格是:
(let [a 1
b (inc a)]
(* a b))
和相应的monad是
(domonad identity-m
[a 1
b (inc a)]
(* a b))
2。序列Monad(或'for'monad)取自http://onclojure.com/2009/03/06/a-monad-tutorial-for-clojure-programmers-part-2/
我们希望写的表格是:
(for [a (range 5)
b (range a)]
(* a b))
和相应的monad是
(domonad sequence-m
[a (range 5)
b (range a)]
(* a b))
Clojure中的Monad定义
查看源代码,使用clojure monads库 - https://github.com/clojure/algo.monads:
user=>(use 'clojure.algo.monads)
nil
indentity monad:
user=> (source identity-m)
(defmonad identity-m
[m-result identity
m-bind (fn m-result-id [mv f]
(f mv))
])
序列monad:
user=> (source sequence-m)
(defmonad sequence-m
[m-result (fn m-result-sequence [v]
(list v))
m-bind (fn m-bind-sequence [mv f]
(flatten* (map f mv)))
m-zero (list)
m-plus (fn m-plus-sequence [& mvs]
(flatten* mvs))
])
所以我的结论是monad是某种广义的高阶函数,它接受输入函数和输入值,添加自己的控制逻辑并吐出一个可用于'domonad'阻止。
问题1
最后,对于问题:我想学习如何写一个monad并说我想写一个'map monad'来模仿clojure中的'map'形式:
(domonad map-m
[a [1 2 3 4 5]
b [5 6 7 8 9]]
(+ a b))
应该相当于
(map + [1 2 3 4 5] [5 6 7 8 9])
并返回值
[6 8 10 12 14]
如果我查看源代码,它应该给我一些类似于identity-m和sequence-m的代码:
user=> (source map-m)
(defmonad map-m
[m-result ...
m-bind ...
m-zero ...
m-plus ...
])
问题2
我也希望能够定义'reduce-m'以便我可以写:
(domonad reduce-m
[a [1 2 3 4 5]]
(* a))
这可能会给我1 x 2 x 3 x 4 x 5 = 120或
(domonad reduce-m
[a [1 2 3 4 5]
b [1 2 3 4 5]]
(+ a b))
会给我(1 + 2 + 3 + 4 + 5)+(1 + 2 + 3 + 4 + 5)= 30
最后 我是否也能够编写一个模仿juxt函数的'juxt monad',但是我传递了一组函数,而不是传递绑定值。 :
(domonad juxt-m
[a #(+ % 1)
b #(* % 2)]
'([1 2 3 4 5] b a) )
给出
[ [2 2] [4 3] [6 4] [8 5] [9 6] ]
潜在地,我可以用宏做所有这些事情,所以我真的不知道这些'monads'会有多么有用,或者它们甚至被认为是'monad'......拥有互联网上的所有资源,它在我看来,如果我想要正确学习Monads,我必须学习Haskell,现在,学习另一种句法形式太难了。我想我找到了一些可能相关的链接但对我来说太神秘了
请有人可以解释一下!
答案 0 :(得分:9)
您的示例不是monad。 monad表示可组合的计算步骤。在琐碎的身份monad中,计算步骤只是表达式评估。在monad中,一步可能是成功或失败的表达。在序列monad中,step是一个表达式,它产生可变数量的结果(序列的元素)。在作者monad中,计算步骤是表达式评估和日志输出的组合。在状态monad中,计算步骤涉及访问和/或修改一段可变状态。
在所有这些情况下,monad plumbery负责正确组合步骤。 m-result函数打包“plain”值以适合monadic计算方案,m-bind函数将一个计算步骤的结果提供给下一个计算步骤。
在(map + a b)中,没有要组合的计算步骤。没有秩序的概念。它只是嵌套表达式评估。同样适用于减少。
答案 1 :(得分:2)
你的问题不是一种单子。它们看起来更像是一个语法糖,你可以使用宏完成,我甚至不建议你这样做,因为map,reduce等是简单的函数,没有必要使它们的接口复杂。
这些情况不是monad的原因是因为moands是一种包含正常值的放大值。在map和reduce的情况下,你使用的矢量不需要放大,以允许它在map中工作或减少。
在domoand表达式上尝试使用macroexpand会很有帮助。 例如:序列monad示例应扩展为:
(bind (result (range 5))
(fn [a]
(bind (result (range a))
(fn [b] (* a b))
)))
其中bind和result是序列monad m-bind和m-result中定义的函数。 所以基本上在domand得到嵌套之后的向量表达式和向量之后的表达式被使用,因为在上面的情况下,(* ab)被原样调用(只是提供了a和b值)由monad)。在你的map monad示例中,矢量表达式应该是原样的,最后一个表达式(+ a b)应该以某种方式表示(map + a b),这不是monad应该做的。
答案 2 :(得分:2)
我找到了一些非常好的monad资源:
所以从Jim的指南 - http://www.clojure.net/2012/02/06/Legalities/ - 他给出了三个公理来定义'bind-m'和'reduce-m'函数:
<强>身份强> 第一个Monad法可以写成
(m-bind(m-result x)f)等于(f x)
这意味着无论m-result对x做什么使其成为monadic值,在将f应用于x之前m-bind撤消。因此关于m-bind,m-result有点像一个身份函数。或者在类别理论术语中,它的单位。这就是为什么有时你会看到它被命名为'unit'。
反向身份第二个Monad法可以写成
(m-bind mv m-result)等于mv,其中mv是monadic值。
这项法律类似于第一部法律的补充。它基本上确保m-result是一个monadic函数,无论m-bind对monadic值进行提取值,m-result都会撤消以创建monadic值。
<强>结合性强> 第三条Monad法可以写成
(m-bind(m-bind mv f)g)等于(m-bind mv(fn [x](m-bind(f x)g))) 其中f和g是monadic函数,mv是monadic值。
这条法律所说的是f是否应用于mv然后将g应用于结果,或者是否使用f和g的组合创建新的monadic函数无关紧要,然后将其应用于MV。无论哪种顺序,结果值都是相同的monadic值。换句话说,m-bind是左右联合的。
在http://www.clojure.net/2012/02/04/Sets-not-lists/中,他给出了一个monad,它将集合作为输入而不是列表。将通过所有的例子......