40

我开始了我的 Grand Haskell Crusade (GHC :) ),我对 monads 和 IO 函数有点困惑。谁能简单地解释这两个功能之间的区别是什么?

f1 = do x <- [1,2]
        [x, x+1] -- this is monad, right?

f2 = do x <- [1,2]
        return [x, x+1]

结果是:

*Main> f1
[1,2,2,3]

*Main> f2
[[1,2],[2,3]]
4

6 回答 6

45

要了解为什么您会得到出现的特定答案,脱糖的解释非常有帮助。让我用一些关于发展对 Haskell 代码的看法的一般性建议来补充它们。

Haskell 的类型系统没有区分两个可分离的“道德”目的:

  • [x]的类型,它们是具有从中提取的元素的列表x
  • [x]允许优先选择的元素的计算类型x

这两个概念具有相同表示的事实并不意味着它们扮演相同的角色。在f1中,[x, x+1]扮演着计算的角色,所以它产生的可能性被合并到整个计算产生的选择中:这就是>>=列表 monad 的作用。然而f2,在 中,[x, x+1]它扮演着值的角色,因此整个计算会在两个值(恰好是列表值)之间产生一个优先选择。

Haskell 不使用类型来进行这种区分[你现在可能已经猜到我认为应该这样做,但那是另一回事了]。相反,它使用语法。所以你需要在阅读代码时训练你的头脑去感知价值和计算的角色。该do表示法是一种用于构造计算的特殊语法。里面的do内容是从以下模板工具包构建的:

用于计算的拼图

三个蓝色的部分进行do计算。我已将计算孔标记为蓝色,将值孔标记为红色。这并不意味着是一个完整的语法,只是一个关于如何在你的脑海中感知代码片段的指南。

实际上,您可以在蓝色位置编写任何旧表达式,只要它具有适当的单子类型,并且这样生成的计算将根据需要合并到整体计算中>>=。在您的f1示例中,您的列表位于蓝色位置,并被视为优先选择。

类似地,你可以在红色的地方写表达式,这些地方很可能有单子类型(如本例中的列表),但它们将被视为相同的值。这就是发生的情况f2:实际上,结果的外括号是蓝色的,但内括号是红色的。

训练您的大脑在阅读代码时进行值/计算分离,以便您本能地知道文本的哪些部分在做什么。一旦你重新编程了你的头脑,和之间的区别f1看起来f2就完全正常了!

于 2012-07-04T09:46:40.793 回答
30

这里的其他答案是正确的,但我想知道它们是否不是你所需要的......我会尽量保持简单,只有两点:


第 1 点return在 Haskell 语言中并不是什么特别的东西。它不是关键字,也不是其他东西的语法糖。它只是类型类中的一个函数Monad。它的签名很简单:

return :: a -> m a

m我们当时谈论的单子在哪里。它需要一个“纯”值并将其塞入您的 monad。(顺便说一句,还有另一个函数叫做pure它基本上是return...的同义词我更喜欢它,因为它的名字更明显!)无论如何,如果m是 list monad,那么return有这种类型:

return :: a -> [a]

如果有帮助,您可以考虑类型 synonym ,这可能会使我们要替换的东西type List a = [a]更加明显。无论如何,如果您要自己实现,那么实现它的唯一合理方法是获取一些值(任何类型)并将其单独粘贴在列表中:Listmreturna

return a = [a]

所以我可以return 1在 list monad 中说,我会得到[1]. 我也可以说return [1, 2, 3],我会得到[[1, 2, 3]]


第 2 点IO是一个单子,但并非所有单子都是IO. 许多 Haskell 教程似乎主要出于历史原因将这两个主题混为一谈(顺便说一句,同样令人困惑的历史原因导致return名称如此糟糕)。听起来您可能对此有一些(可以理解的)困惑。

在您的代码中,您在 list monad 中,因为您编写了do x <- [1, 2]. 相反,如果你写do x <- getLine了例如,你会在IOmonad 中(因为getLine返回IO String)。无论如何,你在 list monad 中,所以你得到了return上面描述的 list 的定义。您还可以得到列表的 定义>>=,它只是(的翻转版本)concatMap,定义为:

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)

其他发布的答案几乎涵盖了这里 :) 我知道我没有直接回答你的问题,但我希望这两点能解决你可能会感到困惑的基本问题。

于 2012-07-05T04:25:32.287 回答
24

使用bindreturn重写代码时很容易看到:

[1,2] >>= (\x->        [x,x+1]) === concatMap (\x-> [  x,x+1  ]) [1,2]

[1,2] >>= (\x-> return [x,x+1]) === concatMap (\x-> [ [x,x+1] ]) [1,2]

您的第一个代码等同于调用join第二个代码的结果,删除由 引入的一个单子“层” return :: a -> m a,将正在使用的单子的“列表”与您的值的“列表”混为一谈。例如,如果您要退回一对,那么省略:return

                                     -- WRONG: type mismatch
[1,2] >>= (\x->        (x,x+1)) === concatMap (\x-> (  x,x+1  )) [1,2]
                                     -- OK:
[1,2] >>= (\x-> return (x,x+1)) === concatMap (\x-> [ (x,x+1) ]) [1,2]

或者,我们可以使用join/fmap重写:

ma >>= famb === join (fmap famb ma)   -- famb :: a -> m b, m ~ []

join (fmap (\x->        [x,x+1]) [1,2]) = concat [ [  x,x+1  ] | x<-[1,2]]
join (fmap (\x->        (x,x+1)) [1,2]) = concat [ (  x,x+1  ) | x<-[1,2]]  -- WRONG
join (fmap (\x-> return [x,x+1]) [1,2]) = concat [ [ [x,x+1] ] | x<-[1,2]]

                                                         =  [y | x<-[1,2], y<-[ x,x+1 ]]
                                            {- WRONG -}  =  [y | x<-[1,2], y<-( x,x+1 )]
                                                         =  [y | x<-[1,2], y<-[[x,x+1]]]
于 2012-07-04T06:28:49.300 回答
14

List 类型 ( []) 是一个 monad,是的。

现在,记住是什么return。这从它的类型签名很容易看出:return :: Monad m => a -> m a. 让我们将列表类型替换为:return :: a -> [a]。所以这个函数接受一些值并只返回该值的列表。它相当于\ x -> [x].

所以在第一个代码示例中,最后有一个列表:[x, x+1]. 在第二个示例中,您有一个嵌套列表:一个列表来自[x, x + 1]另一个列表来自return。在这种情况下,return [x, x + 1]可以重写该行。[[x, x + 1]]

最后,结果是所有可能结果的列表。也就是说,我们连接 as 的结果和asx1结果(感谢该行)。所以在第一种情况下,我们连接两个列表;在第二种情况下,我们连接两个列表列表,因为额外的将结果包装在一个额外的列表中。x2x <- [1,2]return

于 2012-07-04T06:30:26.153 回答
8

do语法脱糖为等价物

f1 = [1,2] >>= \x -> [x, x+1]
f2 = [1,2] >>= \x -> return [x, x+1]

现在,>>=来自Monad课堂,

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a

>>=和两者f1中的 LHSf2[a]a默认为Integer),所以我们真的在考虑

instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    ...

这遵循相同的单子法则,但与单子不同,

instance Monad IO where ...

对于其他单子,>>=所以不要盲目地将你所知道的应用于另一个,好吗?:)

instance Monad []因此在 GHC 中定义

instance Monad [] where
    m >>= k = foldr ((++) . k) [] m
    return x = [x]
    ...

但它可能更容易[]理解>>=

instance Monad [] where
    m >>= k = concatMap k m

如果你把它应用到原件上,你会得到

f1 = concatMap (\x -> [x, x+1]) [1,2]
f2 = concatMap (\x -> [[x, x+1]]) [1,2]

很清楚为什么 和 的价值f1f2什么。

于 2012-07-04T06:30:48.027 回答
1

我的理解是做

在 List monad 中时返回 [1,2] 与做的相同

func :: Maybe (Maybe Int)
func = return $ Just 1

这就是为什么你最终得到包装列表的原因,因为 [] 只是语法糖,对吗?

当他真正想做的时候

func :: Maybe Int
func = return 5

或者

func = Just 5

我认为可能更容易看到 Maybe monad 发生了什么。

所以当你这样做时

return [1,2]

你正在做同样的事情

[ [1,2] ]
于 2012-07-05T15:31:13.730 回答