在列表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语言中不是特别的东西 。 这不是一个关键字,它不是其他语言的语法糖。 这只是Monadtypes类的一部分。 它的签名很简单:

 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发生的情况:结果的外括号是蓝色的,但是内括号是红色的。

当你阅读代码时,训练你的大脑使价值/计算分离,以便你本能地知道哪部分文本正在做什么工作。 一旦你重新编程头部, f1f2之间的区别看起来完全正常!

使用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] 

你的第一个代码相当于在第二个结果上调用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 

f1f2>>=的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] 

很明显为什么f1f2的值就是这样。

我的理解是这样做的

在列表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] ]