在列表monad中使用return和not使用return
我开始了我的大哈斯克尔十字军(GHC :),我对单子和IOfunction有些困惑。 任何人都可以解释这两个函数有什么区别吗?
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]]
这里的其他答案是正确的,但我不知道他们是不是你所需要的…我会尽量保持这个尽可能简单,只有两点:
要点1. return
在Haskell语言中不是特别的东西 。 这不是一个关键字,它不是其他语言的语法糖。 这只是Monad
types类的一部分。 它的签名很简单:
return :: a -> ma
其中m
是我们正在讨论的那个monad。 它需要一个“纯粹”的价值,并将其堵塞到你的monad中。 (顺便说一句,还有另外一个函数叫pure
,基本上就是return
的同义词…我更喜欢它,因为这个名字更明显!)无论如何,如果m
是列表monad,那么return
就是这样的:
return :: a -> [a]
如果有帮助的话,你可以考虑types同义词type List a = [a]
,这可能会让List
稍微明显一点,就是我们正在替代m
。 无论如何,如果你要实现自己的return
,你实现它的唯一合理的方式是通过采取一些价值(不pipe是哪种typesa
),并将其固定在列表中:
return a = [a]
所以我可以说在列表monad return 1
,我会得到[1]
。 我同样可以说return [1, 2, 3]
,我会得到[[1, 2, 3]]
。
第二点: IO
是单子,但不是所有单子都是IO
。 许多Haskell教程似乎将这两个主题归结为历史原因(顺便说一句,同样令人困惑的历史原因导致return
如此糟糕的命名)。 听起来你可能会有一些(可以理解的)困惑。
在你的代码中,你在列表monad中,因为你写了do x <- [1, 2]
。 例如,如果您已经写了do x <- getLine
,那么您将处于IO
monad(因为getLine
返回IO String
)。 无论如何,你在单子列表中,所以你得到了上面描述的return
列表的定义。 您还可以得到>>=
列表定义,这只是( concatMap
的翻转版本),定义如下:
concatMap :: (a -> [b]) -> [a] -> [b] concatMap f xs = concat (map f xs)
其他发布的答案几乎涵盖了从这里:)我知道我没有直接回答你的问题,但我希望这两点,而不是解决你可能会发现混淆基本的东西。
为了明白为什么你会得到特定的答案,这个狡猾的解释是非常有帮助的。 让我补充一点关于发展对Haskell代码的看法的一般意见。
Haskell的types系统不区分两个可分离的“道德”目的:
-
[x]
是从x
抽取元素的值的types -
[x]
允许优先select的x
元素的计算types
这两个概念具有相同的表示这一事实并不意味着它们扮演着相同的angular色。 在f1
, [x, x+1]
扮演着计算的angular色,所以它产生的可能性被合并到整个计算生成的select中:这就是列表monad的>>=
。 然而,在f2
, [x, x+1]
扮演着价值的angular色,因此整个计算生成了两个值之间的优先select(恰好是列表值)。
Haskell不使用types来作出这种区分[你现在可能已经猜到了,我认为它应该,但这是另一个故事]。 相反,它使用语法。 所以当你阅读代码时,你需要训练自己的头脑去感知价值和计算angular色。 符号是构造计算的特殊语法。 do
是从以下模板套件中构build的:
三块蓝色的棋子使得计算成为可能。 我用蓝色标记了计算孔,红色标出了数值孔。 这并不意味着是一个完整的语法,只是一个如何感知您的头脑中的代码块的指南。
事实上,如果它有一个适当的一元types,你可以在蓝色的地方写出任何旧的expression式,如此生成的计算将根据需要使用>>=
合并到整体计算中。 在你的f1
例子中,你的列表是在一个蓝色的地方,并被视为优先select。
同样的,你也可以在红色的地方写下expression式,这些地方很可能有一元types(比如本例中的列表),但是它们将被视为值都是一样的。 这就是在f2
发生的情况:结果的外括号是蓝色的,但是内括号是红色的。
当你阅读代码时,训练你的大脑使价值/计算分离,以便你本能地知道哪部分文本正在做什么工作。 一旦你重新编程头部, f1
和f2
之间的区别看起来完全正常!
使用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 -> ma
引入的一个monadic“layer”, 将正在使用的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 -> mb, 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]]]
列表types( []
)是一个monad,是的。
现在,记住什么是return
。 这很容易从它的types签名中看到: return :: Monad m => a -> ma
。 让我们来replace列表types: return :: a -> [a]
。 所以这个函数需要一些值,并只返回该值的列表。 这相当于\ x -> [x]
。
所以在第一个代码示例中,最后有一个列表: [x, x+1]
。 在第二个示例中,您有一个嵌套列表:一个列表来自[x, x + 1]
, 另一个列表来自return
。 在这种情况下return [x, x + 1]
行return [x, x + 1]
可以重写为[[x, x + 1]]
。
最后,结果是所有可能结果的列表。 也就是说,我们将x
的结果连接为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 (>>=) :: ma -> (a -> mb) -> mb return :: a -> ma
在f1
和f2
中>>=
的LHS是[a]
(其中a
已经默认为Integer
),所以我们真的在考虑
instance Monad [] where (>>=) :: [a] -> (a -> [b]) -> [b] ...
这遵守相同的monad法 ,但是是不同的monad,
instance Monad IO where ...
而>>=
对于其他的monad,所以不要盲目地将你所知道的关于另一个应用到另一个,好吗? 🙂
instance Monad []
是在GHC中定义的
instance Monad [] where m >>= k = foldr ((++) . k) [] m return x = [x] ...
但可能更容易理解[]
的>>=
as
instance Monad [] where m >>= k = concatMap km
如果你把这个应用到原来的,你会得到
f1 = concatMap (\x -> [x, x+1]) [1,2] f2 = concatMap (\x -> [[x, x+1]]) [1,2]
很明显为什么f1
和f2
的值就是这样。
我的理解是这样做的
在列表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] ]