在列表monad中使用return而不是return

时间:2012-07-04 06:07:49

标签: haskell monads

我开始了我的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]]

6 个答案:

答案 0 :(得分:42)

为了了解为什么你会得到特定的答案,这些令人费解的解释非常有用。让我用一些关于开发Haskell代码感知的一般建议来补充它们。

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

  • [x] 的类型,这些列表包含从x
  • 中提取的元素
  • [x] x元素的计算类型,允许优先选择

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

Haskell不使用类型来区分[你现在可能已经猜到了我认为应该这样,但那是另一个故事]。相反,它使用语法。因此,当您阅读代码时,您需要训练自己的头脑来感知价值和计算角色。 do表示法是构造计算的特殊语法。 do内部的内容是根据以下模板工具包构建的:

jigsaw pieces for computations

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

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

类似地,您可以在红色位置编写表达式,这些表达式可能具有monadic类型(在本例中为列表),但它们将被视为完全相同的值。这就是f2中发生的事情:事实上,结果的外括号是蓝色的,但内括号是红色的。

训练你的大脑在你阅读代码时使值/计算分离,这样你就会本能地知道文本的哪些部分正在做哪个工作。一旦你重新编程了你的头,f1f2之间的区别似乎是完全正常的!

答案 1 :(得分:26)

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


点1. return在Haskell语言中不是一件特别的事情。它不是一个关键字,它不是其他东西的语法糖。它只是Monad类型类的一部分。它的签名很简单:

return :: a -> m a

其中m是我们当时谈论的那个monad。它需要一个“纯粹”的价值并将其加入你的monad。 (顺便提一下,还有一个名为pure的函数,它基本上是return的同义词...我更喜欢它,因为名称更明显!)无论如何,如果m是列表monad,然后return有这种类型:

return :: a -> [a]

如果它有帮助,你可以想到类型同义词type List a = [a],这可能会使List更为明显,我们正在替换m。无论如何,如果你要自己实现return,你实现它的唯一合理方法是取一些值(任何类型a)并将它自己放在一个列表中:

return a = [a]

所以我可以在列表monad中说return 1,我会得到[1]。我也可以说return [1, 2, 3],我会得到[[1, 2, 3]]


第2点IO是一个monad,但并非所有monad都是IO许多Haskell教程似乎主要是出于历史原因将这两个主题混为一谈(顺便提一下,同样令人困惑的历史原因导致return命名如此糟糕)。听起来你可能会对此产生一些(可理解的)混淆。

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

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

其他发布的答案几乎已经从这里覆盖了:)我知道我没有直接回答你的问题,但我希望这两点反而解决了你可能会发现令人困惑的基本问题。

答案 2 :(得分:24)

使用 bind 返回重新编写代码时很容易看到:

[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引入的一个“monadic”图层, conflating 正在使用的monad的“list”,其值为“list”。如果你要退回一对,那么省略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]]]

答案 3 :(得分: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]]

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

答案 4 :(得分: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

>>=f1f2的LHS为[a](其中a默认为Integer),因此我们我真的在考虑

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

这遵循相同的monad laws,但是与

不同的monad
instance Monad IO where ...

>>=对于其他monad,所以不要盲目地将你对彼此的了解应用到另一个,好吗? :)

instance Monad []因此在GHC中定义

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

但可能更容易理解[]的{​​{1}}

>>=

如果你拿这个并将它应用到原版,你得到

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

并且很清楚为什么f1 = concatMap (\x -> [x, x+1]) [1,2] f2 = concatMap (\x -> [[x, x+1]]) [1,2] f1的值是它们的原因。

答案 5 :(得分:1)

我的理解是做着

当List monad与

相同时,

返回[1,2]

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

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

他真的想做什么

func :: Maybe Int
func = return 5

func = Just 5

我认为可能会更容易看到可能的monad。

所以当你这样做时

return [1,2]

你做的和

一样
[ [1,2] ]