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

Jak*_* M. 37 monads haskell

我开始了我的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]
Run Code Online (Sandbox Code Playgroud)

结果是:

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

*Main> f2
[[1,2],[2,3]]
Run Code Online (Sandbox Code Playgroud)

pig*_*ker 42

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

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

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

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

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

用于计算的拼图碎片

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

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

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

当你阅读代码时,训练你的大脑使价值/计算分离,这样你就会本能地知道文本的哪些部分正在做哪个工作.一旦你重新设定你的头,之间的区别f1,并f2会似乎完全正常!

  • @WillNess这似乎是我所倡导的方式?我非常小心,不要说我在提倡什么.请放心,我不是在鼓吹你的建议.问题是如何将类型切割为"效果权限"和"值类型".Haskell允许两种方式:在红色位置,唯一的效果是偏好,值部分是整个类型; 在蓝色位置,效果部分应用于值部分.术语的语法选择使用哪种类型分析.我宁愿扩展类型的语法以标记效果/值边界,但是以可重新协商的方式. (2认同)

mer*_*ict 26

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


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

return :: a -> m a
Run Code Online (Sandbox Code Playgroud)

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

return :: a -> [a]
Run Code Online (Sandbox Code Playgroud)

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

return a = [a]
Run Code Online (Sandbox Code Playgroud)

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


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

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

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)
Run Code Online (Sandbox Code Playgroud)

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


Wil*_*ess 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]
Run Code Online (Sandbox Code Playgroud)

你的第一个代码就等于调用第二个代码join的结果,删除一个monadic"layer" return :: a -> m a,正在使用的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]
Run Code Online (Sandbox Code Playgroud)

或者,我们可以使用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]]]
Run Code Online (Sandbox Code Playgroud)


Tik*_*vis 14

List type([])是一个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]].

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


eph*_*ent 8

do语法贬低为等价物

f1 = [1,2] >>= \x -> [x, x+1]
f2 = [1,2] >>= \x -> return [x, x+1]
Run Code Online (Sandbox Code Playgroud)

现在,>>=来自Monad全班,

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a
Run Code Online (Sandbox Code Playgroud)

和的LHS >>=在这两个f1f2[a](其中a默认了Integer),所以我们真的考虑

instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    ...
Run Code Online (Sandbox Code Playgroud)

这遵循相同的monad法则,但是是不同的monad,

instance Monad IO where ...
Run Code Online (Sandbox Code Playgroud)

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

instance Monad [] 因此在GHC中定义

instance Monad [] where
    m >>= k = foldr ((++) . k) [] m
    return x = [x]
    ...
Run Code Online (Sandbox Code Playgroud)

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

instance Monad [] where
    m >>= k = concatMap k m
Run Code Online (Sandbox Code Playgroud)

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

f1 = concatMap (\x -> [x, x+1]) [1,2]
f2 = concatMap (\x -> [[x, x+1]]) [1,2]
Run Code Online (Sandbox Code Playgroud)

并且很明显为什么它们的价值f1f2它们是什么.