我开始了我的 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]
更加明显。无论如何,如果您要自己实现,那么实现它的唯一合理方法是获取一些值(任何类型)并将其单独粘贴在列表中:List
m
return
a
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
了例如,你会在IO
monad 中(因为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
结果(感谢该行)。所以在第一种情况下,我们连接两个列表;在第二种情况下,我们连接两个列表列表,因为额外的将结果包装在一个额外的列表中。x
2
x <- [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] ]