The return
function in Haskell has little to do with the return
keyword in imperative programming languages—it’s just an ordinary function with an ordinary type signature:
return :: Monad m => a -> m a
Basically, return
takes any old value and “lifts” it into a monad. It’s a little clearer what this function does when you replace the m
with a concrete type, like Maybe
:
return :: a -> Maybe a
There’s only one implementation of the above function, and that’s Just
, so return = Just
for the Maybe
monad.
In your case, you are using the function monad, (->) r
, also often called the “reader” monad. Performing the same substitution as with Maybe
, we get the following signature:
return :: a -> r -> a
This function also has only one implementation, which is to ignore its second argument and return its first. This is what const
does, so return = const
for functions.
The question of “when to use return
” is a reasonable one, but it should make more sense after understanding the above: you need to use return
when the value returned from a function passed to >>=
is not monadic, so it needs to be “lifted”. For example, the following would be a type error:
Just 3 >>= \x -> x + 1
Specifically, the right hand side needs to return a Maybe Int
, but it only returns an Int
. Therefore, we can use return
to produce a value of the correct type:
Just 3 >>= \x -> return (x + 1)
However, consider a similar expression. In the following case, using return
would be a type error:
Just [("a", 1), ("b", 2)] >>= \l -> lookup "c"
That’s because the result of the lookup
function is already a Maybe Int
value, so using return
would produce a Maybe (Maybe Int)
, which would be wrong.
Returning to your example using the function monad, (n+)
is already a function of type Int -> Int
, so using return
would be incorrect (it would produce a function of type Int -> Int -> Int
). However, d + d
is just a value of type Int
, so you need return
to lift the value into the monad.
It’s worth noting that, in both cases, you could always replace return
with its underlying implementation. You could use Just
instead of return
when using the Maybe
monad, and you could use const
instead of return
when using the function monad. However, there are two good reasons to use return
:
Using return
lets you write functions that work with more than one kind of monad. That is, return
gets you polymorphism.
It’s extremely idiomatic to use return
to lift plain values into a monadic type, so it’s clearer and less noisy to always see return
instead of seeing many different functions with different names.