What's so special about 'return' keywo

2019-01-22 22:26发布

问题:

When I seemed to understand what return is for in Haskell, I tried to play with different alternatives and it seems that return not only can be used anywhere in the monad chain, but also can be excluded completely

*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 

Even if I omit return in my own instancing, I only get warning...

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'

and I still can use the monad

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

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

So what's so special about the return keyword? Is this about more complex cases where I can not omit it? Or because this is the "right" way to do things even if they can be done differently?

UPDATE: .. or another alternative, I could define my own monadic value constructor

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

and produce another variant of the same chain (with the same result)

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

回答1:

So what's so special about the return keyword?

Firstly, return is not a keyword in Haskell. It is an overloaded function.

Its type is given by:

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

So you see that return is a function that given a value of type a, returns a new value of type m a, where m is some type that is an instance of Monad. Such types include:

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

and many more besides. Instances of 'Monad' should satisfy the following laws:

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

The implementation of a function a -> m a is pretty easy to guess. Here's the definition for the most common monads:

Lists:

 return x = [x]

Maybe

 return x = Just x

So you see that the return is an overloaded function that "lifts" a value into a monadic wrapper. You can thus use it anywhere you can use its definition. E.g.

Prelude> 1 : return 2
[1,2]

or in the do notion (useful when chaining expressions).

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

The real reason to use a monadic return is when composing multiple values in some monad:

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

In the last statement you see how the chain short-circuited once it hit a zero value for the given monad. In this case Nothing.

Summary: return is an overloaded function that lifts a value into a monadic wrapper. You use it when you need to lift values. It is not a control-flow keyword, as it is in imperative languages.



回答2:

I suspect that you're misunderstanding what "return" means in the context of a monad in Haskell. return is a function that takes in an a and returns a "wrapped a" -- that is, the simplest possible instance of the monad. In other languages it is often called Unit. It's not the "control flow" return that you see in C-like languages.

So in your example of the Maybe monad, we have return defined as a function that takes in an a and returns a Maybe a:

return :: a -> Maybe a

And what does it do? if you give it x, it gives you back Just x:

return x = Just x

And now you can use return as a shorthand when you need that function, rather than writing out:

\x -> Just x

It's called return because when you're writing out monads in do notation, it looks like what you'd do in a C-like language.



回答3:

Mike Hartl comment led me to the right direction, although was not so formal imho, so I just post my final understanding what so special about 'return' operator.

Any type class lists operator it supports and there are functions that can work only in this class context (imposed via class constratint symbol =>). So, for example filterM signature

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

shows us that it can be used only in monadic context. The magic is that in the body this function is free to use any operator the class has (>>= and return for Monad) and if an instance (for example my MaybeG ) lacks a method (return in my case) then the function can fail. So when the return is there

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

and when it's commented (see my implementation of MaybeG in the question)

> 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

so imlementation of any operator (return in monad case) is required if one plans to use the instance with functions working with this class (monad in this case) constraint.

I think my initial misunderstanding was due to the fact that most tutorials explains monadic chains without polymorphic (ad hoc) context. This context in my opinion makes monads more powerful and reusable.