Monads in category theory is defined by triples T, unit, flat⟩.
class Monad t where
map :: (a -> b) -> (t a -> t b) -- functorial action
unit :: a -> t a
flat :: t (t a) -> t a
class KleisliTriple t where
unit :: a -> t a
flatMap :: t a -> (a -> t b) -> t b
KleisliTriple flats the structure by the operator: flatMap
(or bind
in Haskell) that is composition of map
and flat
.
However, I always think it's much simpler and easier to understand and implement the Monad conept in functional programming to compose functions by flatten the structure with the object such as flatUnit
that is composition of unit
and flat
.
In this case, flatUnit(flatUnit(x)) = flatUnit(x)
. I actually implemented in this manner in JavaScript, and with flatUnit
and map
(just a legacy functor operator), all the benefit of Monad seems to be obtained.
So, here's my question.
I have kept looking for documents about the kind of flatUnit
formalization in functional programming, but never found it. I understand there's a historical context that Eugenio Moggi who first discovered the relevance of monads in functional programming, and in his paper that happened to be KleisliTriple application, but since Monads are not limited to Kleisli Category and considering the simplicity of flatUnit
, to me it's very strange.
Why is that? and what do I miss?
EDIT:code is removed.
In this answer, I won't dwell on flatUnit
. As others have pointed out, join . return = id
for any monad (it is one of the monad laws), and so there isn't much to talk about it in and of itself. Instead, I will discuss some of the surrounding themes raised in the discussion here.
Quoting a comment:
in other words, functor with a flat structure, it's a monad.
This, I believe, is the heart of the question. A monad need not be a functor with a flat structure, but a functor whose values can be flattened (with join
) in a way that follows certain laws ("a monoid in the category of endofunctors", as the saying goes). It isn't required for the flattening to be a lossless operation (i.e. for join
to be an isomorphism).
Monads whose join
is an isomorphism are called, in category theory parlance, idempotent monads 1. For a Haskell Monad
to be idempotent, though, the monadic values must have no extra structure. That means most monads of immediate interest to a programmer won't be idempotent (in fact, I'm having trouble to think of idempotent Haskell Monad
s that aren't Identity
or identity-like). One example already raised in the comments was that of lists:
join [[1,2],[3,4,5]] = [1,2,3,4,5] -- Grouping information discarded
The function/reader monad gives what I'd say is an even more dramatic illustration:
join (+) = \x -> x + x
This recent question gives an interesting illustration involving Maybe
. The OP there had a function with signature...
appFunc :: Integer -> Integer -> Bool -> Maybe (Integer,Integer)
... and used it like this...
appFunc <$> u <*> v <*> w
... thus obtaining a Maybe (Maybe (Integer, Integer))
result. The two layers of Maybe
correspond to two different ways of failing: if u
, v
or w
are Nothing
, we get Nothing
; if the three of them are Just
-values but appFunc
results in Nothing
, we get Just Nothing
; finally, if everything succeeds we get a Just
-value within a Just
. Now, it might be the case that we, like the author of that question, didn't care about which layer of Maybe
led to the failure; in that case, we would discard that information, either by using join
on the result or by rewriting it as u >>= \x -> v >>= \y -> w >>= \b -> appFunc x y b
. In any case, the information is there for us to use or discard.
Note 1: In Combining Monads by King and Wadler (one of Wadler's papers about monads), the authors introduce a different, and largely unrelated, meaning for "idempotent monad". In their sense, an idempotent monad is one for which (in applicative notation) f <$> u <*> u = (\x -> f x x) <$> u
-- one example would be Maybe
.