I am a C++/Rust programmer, and out of curiosity, I am currently learning Haskell. As expected, I encountered some obstacles while trying to understand Monads:
I have already read about the definition of Monads on Wikibook, including their relationship to the mathematical definition in category theory and the three Monad laws. I also understand that the do
notation is syntactic sugar for Monads, but certain behaviors in do
blocks continue to puzzle me.
In some tutorials, I've noticed that bind
(>>=
) is often explained as applying the second argument (a function) to the value contained within the first argument and return the result of applying the function. However, from the perspective of Monad laws, this doesn't seem to be the only possible implementation. I'm not sure if bind
must always follow this pattern or if it's just a common implementation.
If this pattern is indeed common, then I have to consider other implementations. What confuses me the most is that we know:
do { v <- y; ... } = y >>= (\v -> ...)
So the meaning of <-
here should be directly related to the implementation of >>=
. But if it’s not the common implementation mentioned above, then <-
might no longer carry the semantics of declaration or binding. How should we understand it in such cases?
Unfortunately, as a beginner, I haven't yet thought of an example of an uncommon but law-abiding implementation of bind
, despite my efforts.
Here's the summary of my questions:
bind
(>>=
) that still satisfies the Monad laws?bind
, How to understand the meaning of <-
in a do
notation?I've noticed that bind (>>=) is often explained as applying the second argument (a function) to the value contained within the first argument and return the result of applying the function. However, from the perspective of Monad laws, this doesn't seem to be the only possible implementation.
It must have something like this meaning, based on the types and the monad laws. With forall
s added in:
class Monad m where
return :: forall a . a -> m a
(>>=) :: forall a b . m a -> (a -> m b) -> m b
...
One law that Monads must follow is for any a
and k
, return a >>= k
is equivalent to k a
. This makes it pretty clear that for all monads, a
must be in some meaningful sense be "the value contained in" return a
, and >>=
must take that a
out in some way and "return the result of applying" k
to a
.
Monads can naturally be much more complex. For example, they don't have to contain exactly one a
(see the list monad), nor do they have to have a value contained in them "yet" -- for example, an IO a
is simply a recipe for producing an a
when run. But because of the law that return a >>= k
is equivalent to k a
, we are at least guaranteed that for all monads, there is a way to represent "this value alone," return
, and when you do that, there must be a way to get it out and apply some follow-up function to it.
(>>=)
does not overwrite anything -- you can't reassign the value associated with a variable. To do anything like this, you end up needing something that boils down to recursion, where you're doing a new function call with a different value associated with a variable. But by the monad laws, (>>=)
must in fact always represent some progression of computation that can only move values from the left to the right.