Haskell 列表单子:`>>;=`和“返回”行为

Haskell 列表单子:`>>;=`和“返回”行为,haskell,monads,Haskell,Monads,我刚刚开始研究monad,我不明白为什么这两个表达式的计算结果不同: ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch) [(1,'a'),(1,'b'),(2,'a'),(2,'b')] ghci> return ([1,2],['a','b']) ([1,2],"ab") 类型不同,所以行为不同是合理的 第一个表达式的类型检查为numt=>[(t,Char)] 在(>>=)中使

我刚刚开始研究monad,我不明白为什么这两个表达式的计算结果不同:

ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]


ghci> return ([1,2],['a','b'])
([1,2],"ab")

类型不同,所以行为不同是合理的

第一个表达式的类型检查为
numt=>[(t,Char)]

在(>>=)中使用[]作为单子意味着它推断单子应该是列表单子,并且在
列表单子的上下文中(>>=)是concatMap,返回是(:[])

concatMap (\n -> concatMap (\ch -> [(n, ch)]) ['a', 'b']) [1,2]
返回
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

在你的第二个例子中,真正发生的是

表达式的类型更一般一些:

Prelude> :t return ([1,2],['a','b'])
return ([1,2],['a','b']) :: (Monad m, Num t) => m ([t], [Char])
因为你在GHCi中运行它,所以会发生一些事情。GHCi可以被认为是一个非常大的特殊IO单子。因此,由于没有指定monad,当GHC尝试打印结果时,在本例中,
m
monad将成为
IO

t
也默认为
Integer
,因此结果表达式的类型为
::IO([Integer],[Char])


碰巧,所有使用的类型都有一个
Show
实例,因此GHC可以打印执行
IO
操作的结果,在这种情况下(由于操作返回)与输入相同。

在GHCi中,您可以使用
:t
以交互方式检查表达式的类型。这样做表明表达式具有不同的类型:

ghci> :t [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
  :: (Num t) => [(t, Char)]

ghci> :t return ([1,2],['a','b'])
return ([1,2],['a','b']) :: (Num t, Monad m) => m ([t], [Char])
因此,它们有不同的价值观

可能您对
return
的参数中是否存在单子感到困惑。但是,看看它的类型:

ghci> :t return
return :: Monad m => a -> m a
return
对其参数一无所知——它只接受一个值,任何值,并将其置于默认的一元上下文中


要准确理解计算这些表达式时会发生什么,您需要:

  • Hoogle,查找列表的monad实例,以及
  • 第二个表达式的更具体类型
  • monad实例:

    instance  Monad []  where
        m >>= k             = foldr ((++) . k) [] m
        m >> k              = foldr ((++) . (\ _ -> k)) [] m
        return x            = [x]
        fail _              = []
    
    (我们可以忽略
    >
    失败
    ,因为我们没有使用它们。)

    让我们展开我们的表达式:

    [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
    
    return ([1,2],['a','b'])
    
    所以设置
    m=[1,2]
    k=\n->['a','b']>=\ch->return(n,ch)
    我们得到:

    foldr ((++) . (\n -> ['a','b'] >>= \ch -> return (n,ch))) [] [1,2]
    
    现在要去掉第二个
    >=
    m=['a',b']
    k=\ch->return(n,ch)

    而且
    return
    很容易摆脱:

    foldr ((++) . (\n -> rest)) [] [1,2]
      where
        rest = foldr ((++) . (\ch -> [(n,ch)]) [] ['a', 'b']
    
    另一方面,第二个表达式的值:

    [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
    
    return ([1,2],['a','b'])
    
    取决于你在哪个单子。在单子列表中,它只是变成:

    [([1,2], ['a','b'])] :: [] ([Int], String)
    
    而在单子中,它是:

    Just ([1,2], ['a', 'b']) :: Maybe ([Int], String)
    

    如果你解释为什么你认为它们应该是一样的,你可能会获得一些见解;如果我能说出第二个最喜欢的答案,我肯定会的。