12

当我似乎理解了 Haskell 中 return 的用途时,我尝试使用不同的替代方案,似乎 return 不仅可以在 monad 链中的任何地方使用,而且可以完全排除

*Main> Just 9 >>= \y -> (Just y) >>= \x -> return x
Just 9

*Main> Just 9 >>= \y -> (return y) >>= \x -> (Just y)
Just 9

*Main> Just 9 >>= \y -> (Just y) >>= \x -> (Just x)
Just 9 

即使我在自己的实例中省略 return ,我也只会收到警告......

data MaybeG a = NothingG | JustG a deriving Show 
instance Monad MaybeG where  
    --    return x = JustG x  
        NothingG >>= f = NothingG  
        JustG x >>= f  = f x  
        fail _ = NothingG  

Monad.hs:3:10:
    Warning: No explicit method nor default method for `return'
    In the instance declaration for `Monad MaybeG'

我仍然可以使用单子

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (JustG y)
JustG 9

*Main> JustG 9 >>= \y -> (NothingG) >>= \x -> (JustG y)
NothingG

那么 return 关键字有什么特别之处呢?这是关于我不能省略的更复杂的情况吗?还是因为这是做事的“正确”方式,即使它们可以以不同的方式完成?

更新: ..或另一种选择,我可以定义我自己的一元值构造函数

finallyMyLastStepG :: Int -> MaybeG Int
finallyMyLastStepG a = JustG a  

并产生同一链的另一个变体(具有相同的结果)

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (finallyMyLastStepG y)
JustG 9
4

3 回答 3

37

那么 return 关键字有什么特别之处呢?

首先,return不是Haskell的关键字。它是一个重载函数。

其类型由下式给出:

class  Monad m  where
    -- | Sequentially compose two actions, passing any value produced
    -- by the first as an argument to the second.
    (>>=)       :: m a -> (a -> m b) -> m b

    -- | Inject a value into the monadic type.
    return      :: a -> m a

所以你看到这return是一个给定类型值的函数a,返回一个新的类型值m a,其中m某个类型是 的一个实例Monad。此类类型包括:

  • 单子[]
  • 单子I0
  • 单子Maybe
  • 单子STM
  • 单子((->) r)
  • 单子(Either e)
  • 单子(ST s)

还有更多。'Monad' 的实例应满足以下定律:

> return a >>= k  ==  k a
> m >>= return  ==  m
> m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h

函数的实现a -> m a很容易猜到。以下是最常见的 monad 的定义:

列表

 return x = [x]

也许

 return x = Just x

所以你会看到这return是一个重载函数,它把一个值“提升”到一个单子包装器中。因此,您可以在任何可以使用其定义的地方使用它。例如

Prelude> 1 : return 2
[1,2]

或在概念do中(在链接表达式时很有用)。

> do v <- return 7 ; return v :: Maybe Int
Just 7

使用单子的真正原因return是在某些单子中组合多个值时:

Prelude> do x <- return 1 ; y <- return 2 ; return (x + y) :: Maybe Int
Just 3
Prelude> do x <- Nothing  ; y <- return 2 ; return y
Nothing

在最后一个语句中,您会看到链在给定 monad 达到零值后是如何短路的。在这种情况下Nothing

摘要:return是一个重载函数,它将一个值提升到一个单子包装器中。当您需要提升价值时,您可以使用它。它不是控制流关键字,因为它在命令式语言中。

于 2013-03-10T16:34:08.567 回答
13

我怀疑您误解了在 Haskell 的单子上下文中“返回”的含义。return 是一个函数,它接受a并返回一个“包装的a”——也就是说,monad 的最简单的可能实例。在其他语言中,它通常被称为Unit. return这不是您在类 C 语言中看到的“控制流” 。

因此,在您的Maybemonad 示例中,我们将 return 定义为一个接受 ana并返回 a的函数Maybe a

return :: a -> Maybe a

它有什么作用?如果你给它x,它会给你回报Just x

return x = Just x

现在,return当您需要该功能时,您可以将其用作速记,而不是写出:

\x -> Just x

之所以这样称呼它,是return因为当你用符号写出 monad 时do,它看起来就像你在类似 C 的语言中所做的那样。

于 2013-03-10T15:35:49.240 回答
2

Mike Hartl 的评论将我引向了正确的方向,虽然不是那么正式的恕我直言,所以我只是发布了我的最终理解,“return”运算符有什么特别之处。

任何类型类都列出了它支持的运算符,并且有些函数只能在此类上下文中工作(通过类约束符号 => 强加)。因此,例如 filterM 签名

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] 

向我们展示了它只能在单子上下文中使用。神奇的是,在主体中,该函数可以自由使用该类具有的任何运算符(>>= 并返回 Monad),如果一个实例(例如我的 MaybeG )缺少方法(在我的情况下返回),那么该函数可以失败。所以当回报在那里

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 
JustG [2,1]

当它被评论时(请参阅我在问题中对 MaybeG 的实现)

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 

*** Exception: Monad.hs:3:10-21: No instance nor default method for class operation GHC.Base.return

因此,如果计划将实例与使用此类(在本例中为 monad)约束的函数一起使用,则需要实现任何运算符(在 monad 情况下返回)。

我认为我最初的误解是由于大多数教程在没有多态(即席)上下文的情况下解释单子链。在我看来,这种情况使 monad 更加强大和可重用。

于 2013-03-12T10:03:08.057 回答