Monads not with “flatMap” but “flatUnit”? [closed]

2019-10-03 01:29发布

问题:

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.

回答1:

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 Monads 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.