我试图理解Haskell monad,阅读"Monads for the Curious Programmer".我遇到了List Monad的例子:
tossDie=[1,2,3,4,5,6]
toss2Dice = do
n <- tossDie
m <- tossDie
return (n+m)
main = print toss2Dice
Run Code Online (Sandbox Code Playgroud)
顺便do块产生m为36元素的列表我有点明白-它的每个元素映射n为6个元素的列表,然后串接这些列表.我不明白的是,从6个元素列表到36个元素n的存在是如何改变的m <- tossDie.显然"我们先绑定n然后绑定m"是错误的理解,但是什么是正确的?
我还不完全清楚如何在do块中应用双参数函数.我怀疑这是一个卷曲的情况,但我对它究竟是如何工作有点模糊.
有人可以解释一下上面的两个谜团吗?
kqr*_*kqr 10
我假设有趣的是这个:
toss2Dice = do
n <- tossDie
m <- tossDie
return (n+m)
Run Code Online (Sandbox Code Playgroud)
这有点等同于以下Python:
def toss2dice():
for n in tossDie:
for m in tossDie:
yield (n+m)
Run Code Online (Sandbox Code Playgroud)
当涉及列表monad时,您可以<-在do notation中查看绑定箭头()作为传统的命令式"foreach"循环.之后的一切
n <- tossDie
Run Code Online (Sandbox Code Playgroud)
属于该foreach循环的"循环体",因此将为tossDie分配给的每个值计算一次n.
如果你想要从do符号到实际的绑定运算符>>=,它看起来像这样:
toss2Dice =
tossDie >>= (\n ->
tossDie >>= (\m ->
return (n+m)
)
)
Run Code Online (Sandbox Code Playgroud)
注意"内环体"如何
(\n ->
tossDie >>= (\m ->
return (n+m)
)
)
Run Code Online (Sandbox Code Playgroud)
将为每个值执行一次tossDie.这几乎等同于嵌套的Python循环.
技术mumbo-jumbo:你从绑定箭头获得"foreach"循环的原因与你正在使用的特定monad有关.箭头对于不同的monad来说意味着不同的东西,并且要知道它们对于特定monad的意义,你必须做一些调查并弄清楚monad是如何工作的.
箭头变得对于绑定操作符的调用,>>=对于不同的monad <-也有不同的工作方式- 这就是绑定箭头对不同的monad也有不同的工作原因!
在列表monad的情况下,绑定操作符>>=将左侧列表和一个向右返回列表的函数,并将该函数应用于列表的每个元素.如果我们想以繁琐的方式将列表中的每个元素加倍,我们可以想象这样做
?> [1, 2, 3, 4] >>= \n -> return (n*2)
[2,4,6,8]
Run Code Online (Sandbox Code Playgroud)
(return需要使类型工作.>>=需要一个返回列表的函数,并且return对于列表monad,将在列表中包装一个值.)为了说明一个可能更强大的示例,我们可以从想象函数开始
?> let posneg n = [n, -n]
?> posneg 5
[5,-5]
Run Code Online (Sandbox Code Playgroud)
然后我们可以写
?> [1, 2, 3, 4] >>= posneg
[1,-1,2,-2,3,-3,4,-4]
Run Code Online (Sandbox Code Playgroud)
计算-4到4之间的自然数.
名单单子这样工作的原因是绑定运营商的这种特定的行为>>=,并return使得单子法律举行.monad法则对我们(也许是冒险的编译器)很重要,因为它们让我们以我们知道不会破坏任何东西的方式改变代码.
一个非常可爱的副作用是,它使列表非常方便地表示值的不确定性:假设您正在构建一个OCR thingey,它应该查看图像并将其转换为文本.您可能会遇到一个可能是4或A或H的角色,但您不确定.通过让OCR thingey在列表monad中工作并返回列表,['A', '4', 'H']你已经覆盖了你的基础.实际上使用扫描的文本然后变得非常容易和可读与do列表monad的符号.(它看起来好像你正在使用单个值,而实际上你只是生成所有可能的组合!)
com*_*orm 10
对于列表(例如tossDie),do符号的作用类似于列表推导 - 也就是说,好像每个变量绑定都是嵌套foreach循环.
do-block表达式:
toss2Dice = do { n <- tossDie; m <- tossDie; return (n+m) }
Run Code Online (Sandbox Code Playgroud)
与此列表理解做同样的事情:
toss2Dice = [ n+m | n <- tossDie, m <- tossDie ]
Run Code Online (Sandbox Code Playgroud)
结果与以下命令式伪代码相当:
toss2Dice = []
foreach n in tossDie:
foreach m in tossDie:
toss2Dice.push_back(n+m)
Run Code Online (Sandbox Code Playgroud)
除了Haskell示例根据需要懒洋洋地产生结果,而不是急切地,一次性地产生结果.
如果你查看monad实例的列表,你可以看到它是如何工作的:
instance Monad [] where
xs >>= f = concat (map f xs)
return x = [x]
Run Code Online (Sandbox Code Playgroud)
从do块的开头开始,每个变量绑定在块的其余部分创建一个循环:
do { n <- tossDie; m <- tossDie; return (n+m) }
===> tossDie >>= \n -> do { m <- tossDie; return (n+m) }
===> concat ( map (\n -> do { m <- tossDie; return (n+m) }) tossDie )
Run Code Online (Sandbox Code Playgroud)
请注意, map函数会迭代列表中的项目tossDie,并且会生成结果concat.映射函数是do块的其余部分,因此第一个绑定有效地在其周围创建外部循环.
附加绑定会连续创建嵌套循环; 最后,该return函数从每个计算值中创建一个单例列表,(n+m)以便"绑定"函数>>=(需要列表)可以正确地连接它们.