我开始了我的 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]]
我开始了我的 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]]
要了解为什么您会得到出现的特定答案,脱糖的解释非常有帮助。让我用一些关于发展对 Haskell 代码的看法的一般性建议来补充它们。
Haskell 的类型系统没有区分两个可分离的“道德”目的:
[x]值的类型,它们是具有从中提取的元素的列表x[x]允许优先选择的元素的计算类型x这两个概念具有相同表示的事实并不意味着它们扮演相同的角色。在f1中,[x, x+1]扮演着计算的角色,所以它产生的可能性被合并到整个计算产生的选择中:这就是>>=列表 monad 的作用。然而f2,在 中,[x, x+1]它扮演着值的角色,因此整个计算会在两个值(恰好是列表值)之间产生一个优先选择。
Haskell 不使用类型来进行这种区分[你现在可能已经猜到我认为应该这样做,但那是另一回事了]。相反,它使用语法。所以你需要在阅读代码时训练你的头脑去感知价值和计算的角色。该do表示法是一种用于构造计算的特殊语法。里面的do内容是从以下模板工具包构建的:

三个蓝色的部分进行do计算。我已将计算孔标记为蓝色,将值孔标记为红色。这并不意味着是一个完整的语法,只是一个关于如何在你的脑海中感知代码片段的指南。
实际上,您可以在蓝色位置编写任何旧表达式,只要它具有适当的单子类型,并且这样生成的计算将根据需要合并到整体计算中>>=。在您的f1示例中,您的列表位于蓝色位置,并被视为优先选择。
类似地,你可以在红色的地方写表达式,这些地方很可能有单子类型(如本例中的列表),但它们将被视为相同的值。这就是发生的情况f2:实际上,结果的外括号是蓝色的,但内括号是红色的。
训练您的大脑在阅读代码时进行值/计算分离,以便您本能地知道文本的哪些部分在做什么。一旦你重新编程了你的头脑,和之间的区别f1看起来f2就完全正常了!
这里的其他答案是正确的,但我想知道它们是否不是你所需要的......我会尽量保持简单,只有两点:
第 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)
其他发布的答案几乎涵盖了这里 :) 我知道我没有直接回答你的问题,但我希望这两点能解决你可能会感到困惑的基本问题。
使用bind和return重写代码时很容易看到:
[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]]]
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 的结果和asx的1结果(感谢该行)。所以在第一种情况下,我们连接两个列表;在第二种情况下,我们连接两个列表列表,因为额外的将结果包装在一个额外的列表中。x2x <- [1,2]return
将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]
很清楚为什么 和 的价值f1是f2什么。
我的理解是做
在 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] ]