我开始了我的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]]
答案 0 :(得分: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
之间的区别似乎是完全正常的!
答案 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
的结果1
和x
的结果连接为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
且>>=
和f1
中f2
的LHS为[a]
(其中a
默认为Integer
),因此我们我真的在考虑
instance Monad [] where
(>>=) :: [a] -> (a -> [b]) -> [b]
...
这遵循相同的monad laws,但是与
不同的monadinstance 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] ]