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.
return
is used to pull values up into your monad - as every other function you just have to look at the types (return
is not what you know from C-like languages) - aside from this: why use the monad-instance of(->) a
here at all? it just obfuscates everything - is this some exercise? - Random Dev\n -> (n+)
already has the desired typeInt -> (Int -> Int)
(your monad is(->) Int
) - but of coursed+d
has not (it has typeInt
) - so you usereturn (d+d) = \ _ -> (d+d)
(rememberthe
\d -> ...` in>>= (\d -> ...)
needs to return a monadic value - that is aInt -> a
here ) - Random Dev(->) a
monad, which is not that much used. To get what's a monad is about I'd recommend you also play with other monads. Start from e.g.Maybe
and[]
andEither e
, and then move toState s
,IO
, etc. Just focusing on one monad will not make you understand the whole picture. - chi