What's so special about 'return' keyword

13,874

Solution 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.

Solution 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.

Solution 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.

Share:
13,874
Maksee
Author by

Maksee

Updated on June 05, 2022

Comments

  • Maksee
    Maksee almost 2 years

    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
    
  • Maksee
    Maksee about 11 years
    Ok, \x -> (Just x) in my third line does exactly the same, it returns the monadic value
  • Admin
    Admin about 11 years
    Is it called Unit? AFAIK Unit as an alternative name for the empty tuple (or more abstractly/generally/correctly, the type with only one value).
  • Maksee
    Maksee about 11 years
    @Eric_Lippert, so the 'return' is a syntax sugar? Seems like nobody ever mentions this.
  • Eric Lippert
    Eric Lippert about 11 years
    @delnan: Yes, "unit" is often used for those purposes as well.
  • Eric Lippert
    Eric Lippert about 11 years
    @Maksee: return is a requirement of the monad pattern. I'm running a series on gentle introduction to monads for object-oriented programmers on my blog right now; on it, I'm saying CreateSimpleM instead of return to keep it clear. You might want to check it out. ericlippert.com/category/monads, start from the bottom.
  • Don Stewart
    Don Stewart about 11 years
    return is not syntax sugar. It is a function. Albeit a polymorphic one.
  • Gabriella Gonzalez
    Gabriella Gonzalez about 11 years
    Technically, Just alone is also a suitable replacement for (\x-> Just x) since Just is an ordinary function.. Also, it's awesome to see Eric Lippert answering a Haskell question.
  • Maksee
    Maksee about 11 years
    Ok, I see that applying "keyword" to "return" is wrong, but mostly my question was about whether should I always use it or I can safely use the other way of doing the same. In other words seeing that the same result can be returned several other ways, why should I follow the convention?
  • Maksee
    Maksee about 11 years
    So, I can replace your last example with do x <- (Just 1); y <-(Just 2);(Just (x+y)) and get the same result without using "return"
  • Vitus
    Vitus about 11 years
    @Maksee: Yup, it does the same thing.
  • firefrorefiddle
    firefrorefiddle about 11 years
    @Maksee It does the same thing, but it has one important shortcoming: It only works for Maybes. If you use return you can use the same code for any monad (try [], for example).
  • Maksee
    Maksee about 11 years
    @MikeHartl, great, the same code as in "copy-paste" working differently being pasted in different context or the same code as in function body being different by the polymorphic nature? It is better to be latter :)
  • firefrorefiddle
    firefrorefiddle about 11 years
    @Maksee The same code as in defining a polymorphic function which works for arbitrary monads. This is critical for Haskell's libraries. One couldn't define functions like mapM or sequence, which work for arbitrary monads, without return - you could only ever define them for a single monad. Without this generality, there would be no point in monads, though!
  • pat
    pat about 11 years
    Even when you know the Monad instance you are using, you may not know how it is implemented, so you have to use return to lift a value. For instance, how would you lift a value into IO without return?