Haskell中do块中`<-`的含义

12 monads haskell

我试图理解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)以便"绑定"函数>>=(需要列表)可以正确地连接它们.