I don't use Haskell a lot, but I understand the concept of Monads.
I had been confused by Kleisli triple, and the category, however,
Although Haskell defines monads in terms of the return and bind functions, it is also possible to define a monad in terms of
return
and two other operations,join
andfmap
. This formulation fits more closely with the original definition of monads in category theory. Thefmap
operation, with type(t → u) → M t → M u
, takes a function between two types and produces a function that does the "same thing" to values in the monad. Thejoin
operation, with typeM (M t) → M t
, "flattens" two layers of monadic information into one.
helps me to the background principle of Monads.
The two formulations are related as follows:
fmap f m = m >>= (return . f)
join n = n >>= id
fmap :: (a -> b) -> (m a -> m b)
unit :: a -> m a
join :: m (m a) -> m a
>>= :: m a -> (a -> m b) -> m b
m >>= f = join $ fmap f m
My question is:
I think that since >>=
can be composed of fmap
and join
, a monadic function
a -> m b
is not required and normal functions a -> b
will satisfy the operation, but so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.
Well, shouldn't we just use non-monadic functions, as long as they are endo-functions, for the simplicity? What do I miss?
Related topics are
I'm having a bit of a hard time understanding what your question actually is, but I'll take a crack at it anyway.
I expect you're referring to the "monadic function
a -> m b
" in the type for>>=
, is that correct? Well, let's see what happens when we replace that with a function of typea -> b
:But doesn't this look familiar? It's equivalent to
fmap :: (a -> b) -> m a -> m b
, but with the parameters switched. In fact, the implementation is justx >>= y = fmap y x
, no need forjoin
. So there's your answer: if you use a "normal functiona -> b
" instead of a "monadic functiona -> m b
", you no longer have a monad. Instead, you have aFunctor
.I'm not sure which tutorials you're looking at. In my experience, the nature of tutorials is that they insist on whatever they're a tutorial for. It would be weird if a tutorial for
Monad
s started presenting problems, and then suggesting things other thanMonad
s as solutions; at the very least, that would be out of the tutorial's scope, and a waste of time for anyone reading it to learn aboutMonad
s.Endofunctions are functions of type
a -> a
. Given the context of your question, I think you actually mean pure functions of typea -> b
("pure" as opposed to inherently monadic functions such asreadIORef
that need to be typea -> m b
). If my assumption is wrong, let me know, and I'll edit the question.EDIT:
As suggested in a comment by @duplode, it's likely that you mean endofunctor, which in Haskell is just any type of
Functor
. In this case, the below still applies.In situations where
Monad
isn't necessary, it is often simpler to useApplicative
,Functor
, or just basic pure functions. In these cases, these things should be (and generally are) used in place of aMonad
. For example:To be clear: If it's possible without a monad, and it's simpler and more readable without a monad, then you should do it without a monad! If a monad makes the code more complex or confusing than it needs to be, don't use a monad! Haskell has monads for the sole purpose of making certain complex computations simpler, easier to read, and easier to reason about. If that's not happening, you shouldn't be using a monad.
In a sense, you're right. As every monad
m
is a functor, we can usefmap f
with a functionf :: a -> b
to turn anm a
into anm b
, but there's a catch. What'sb
?I like to think of such an
m
as meaning "plan-to-get", where "plans" involve some sort of additional interaction beyond pure computation. If you have a "plan-to-getInt
" and you want a "plan-to-getString
", you can usefmap
with a function inInt -> String
, but the type of that function tells you that getting theString
from theInt
involves no further interaction.That isn't always so: perhaps the
Int
is a student registration number and theString
is their name, so the plan to convert from one to the other needs an external lookup in some table. Then I don't have a pure function fromInt
toString
, but rather a pure function fromInt
to "plan-to-getString
". If Ifmap
that across my "plan-to-getInt
", that's fine, but I end up with "plan-to-get (plan-to-getString
)" and I need tojoin
the outer and inner plans.The general situation is that we have enough information to compute the plan to get more. That's what
a -> m b
models. In particular, we havereturn :: a -> m a
, which turns the information we have into the plan that gives us exactly that information by taking no further action, and we have(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)
which composes two such things. We also have that(>=>)
is associative and absorbsreturn
on left and right, much the way;
is associative and absorbsskip
in classic imperative programming.It's more convenient to build larger plans from smaller ones using this compositional approach, keeping the number of "plan-to-get" layers a consistent one. Otherwise, you need to build up an n-layer plan with
fmap
, then do the right number ofjoin
s on the outside (which will be a brittle property of the plan).Now, as Haskell is a language with a concept of "free variable" and "scope", the
a
inrepresenting the "overall input information" can just be taken to come from the scope of things we already have, leaving
and we get back "bind", which is the tool that presents the compositional structure in the most programmer-friendly form, resembling a local definition.
To sum up, you can work with
a -> b
, but often you needb
to be "plan-to-get something", and that's the helpful thing to choose if you want to build plans compositionally.There is no reason.
Yes, you're totally right. We don't need to require
>>=
for a monad, we could also requirejoin
instead. The two are totally equivalent. As we can also composejoin = (>>= id)
, we can do eitheror
It doesn't matter which one we use. Admittedly, the latter looks simpler because there is only one higher-order function (
fmap
), in the former the types offmap
and=<<
look too similar.join
gives a better idea of what distinguishes a monad from a functor.Versatility
We can derive
>>=
fromfmap
andjoin
, but we can derivejoin
from>>=
only. In fact, we can even derivefmap
from>>=
andreturn
. So should we say that>>=
is more basic than the other? More powerful? Or maybe just: more convoluted?We would rather like to write
than
The former solution using
>>=
is minimal.Convenience
No. The whole idea of defining the monad typeclass is to ease working with monadic functions. On their own, the
return
/fmap
/join
functions are pretty useless, what we are interested in are other functions that return the monadic data type:tryParse :: String -> Maybe Int
for example.And the whole idea behind monads is that we can arbitrarily chain and nest them, getting back a plain type in the end. After having parsed a number, we need to validate the optional result (giving us another optional result) - in the monad (
fmap validate
) before getting it back out. There are usually no operations that yield nested data directly, we only get nested monad types because we do further monadic operations inside a monadic type. And we'd much rather writethan
That's why
>>=
is more important for using monads in daily life thanjoin
. I would also guess that having to implement>>=
directly, rather than implementjoin
explicitly and have>>=
get derived from it, allows for better (easier) compiler optimisations.