6

Does it make sense to define multiple flatMap (or >>= / bind in Haskell) methods in a Monad? The very few monads I actually use (Option, Try, Either projections) only define one flatMap method.

For exemple, could it make sense to define a flatMap method on Option which would take a function producing a Try? So that Option[Try[User]] would be flattened as Option[User] for exemple? (Considering loosing the exception is not a problem ...)

Or a monad should just define one flatMap method, taking a function which produces the same kind of monad? I guess in this case the Either projections wouldn't be monads? Are they?

Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419

4 Answers4

5

I have once seriously thought about this. As it turns out, such a construct (aside from losing all monadic capabilities) is not really interesting, since it is sufficient to provide a conversion from the inner to the outer container:

joinWith :: (Functor m, Monad m) => (n a -> m a) -> m (n a) -> m a
joinWith i = join . (fmap i)

bindWith :: (Functor m, Monad m) => (n a -> m a) -> m a -> (a -> n a) -> m a
bindWith i x f = joinWith i $ fmap f x

*Main>  let maybeToList = (\x -> case x of Nothing -> []; (Just y) -> [y])
*Main>  bindWith maybeToList [1..9] (\x -> if even x then Just x else Nothing)
[2,4,6,8]
phipsgabler
  • 20,535
  • 4
  • 40
  • 60
  • 2
    This is a good answer. My understanding is that it also fits well with the theory: the `n a -> m a` function represents a natural transformation between the `n` and `m` functors. Assuming it respects the monad operations, it also represents a monad homomorphism between `n` and `m`. – Tikhon Jelvis May 06 '13 at 22:31
  • Sorry as clever your answer are, this do not answer the question which is `Multiple flatMap methods for a single monad ?`. Of course we can find a way to fusion two differents monad into a choosen one but doing this kind of transformation does it lead to a monad too ? And I don't see how your answer can help him to understand why this not a monad. – zurgl May 06 '13 at 23:03
  • this answer is almost perfect, imo. The one further thing I would point out is if you had a function `m a -> (a -> n b) -> n b` that obeyed monadish laws, you could construct a monad morphism `m a -> n b` by passing it a `return` as the second argument. Simillarly, a function like `m a -> (a -> n b) -> m b` can be turned into a monad morphism `n a -> m b` with sligntly more work. – Philip JF May 07 '13 at 03:16
  • @zurgl Why doesn't `bindWith` provide the multiple `flatMap` methods asked for? For each compatible inner type, you get a function behaving like the OP wanted (if I interpreted the question correctly). Especially, `bind == bindWith id`. And it is not a monad simply because it doesn't obey the definition of one. The inner type doesn't even need to be a monad. – phipsgabler May 07 '13 at 08:53
  • Ok you're right as the only way I can be disagree with you is about how I (or you) have interpreted the question ask by the OP. Then it's a useless debate which can only clarify by the OP. Anyway as said you answer is really interesting =) – zurgl May 07 '13 at 09:37
  • I must admit though, that my interpretation is probably biased to what I had previously found when reasoning about an similar problem. I even wanted to post a question equivalent to this one, when I finally found out that things are as simple as I wrote above. – phipsgabler May 07 '13 at 10:01
1

It depends what "make sense" means.

If you mean is it consistent with the monad laws, then it's not exactly clear to me the question entirely makes sense. I'd have to see a concrete proposal to tell. If you do it the way I think you suggest, you'll probably end up violating composition at least in some corner cases.

If you mean is it useful, sure, you can always find cases where such things are useful. The problem is that if you start violating monad laws, you have left traps in your code for the unwary functional (category theory) reasoner. Better to make things that look kind of like monads actually be monads (and just one at a time, though you can provide an explicit way to switch a la Either--but you're right that as written LeftProjection and RightProjection are not, strictly speaking, monads). Or write really clear docs explaining that it isn't what it looks like. Otherwise someone will merrily go along assuming the laws hold, and *splat*.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
1

It doesn't make sense, for a specific data type, as far as I know, you can only have one definition for bind.

In haskell a monad is the following type class,

instance Monad m where  
    return :: a -> m a
    bind   :: m a -> (a -> m b) -> m b

Concretely For list Monad we have,

instance Monad [] where
    return :: a -> [] a
    (>>=)   :: [] a -> (a -> [] b) -> [] b

Now let's consider a monadic function as.

actOnList :: a -> [] b 
 ....

A Use case to illustrate,

$ [1,2,3] >>= actOnList

On the function actOnList we see that a list is a polymorphic type constraint by another type (here []). Then when we speak about the bind operator for list monad we speak about the bind operator defined by [] a -> (a -> [] b) -> [] b.

What you want to achieve is a bind operator defined like [] Maybe a -> (a -> [] b) -> [] b, this not a specialize version of the first one but another function and regarding it type signature i really doubt that it can be the bind operator of any kind of monad as you do not return what you have consumed. You surely go from one monad to another using a function but this function is definitely not another version of the bind operator of list.

Which is why I've said, It doesn't make sense, for a specific data type, as far as I know, you can only have one definition for bind.

zurgl
  • 1,930
  • 1
  • 14
  • 20
1

flatMap or (>>=) doesn't typecheck for your Option[Try[ ]] example. In pseudo-Haskell notation

type OptionTry x = Option (Try x)

instance Monad OptionTry where
  (>>=) :: OptionTry a -> (a -> OptionTry b) -> OptionTry b
  ...

We need bind/flatMap to return a value wrapped in the same context as the input value.

We can also see this by looking at the equivalent return/join implementation of a Monad. For OptionTry, join has the specialized type

instance Monad OptionTry where
  join :: OptionTry (OptionTry a) -> OptionTry a
  ...

It should be clear with a bit of squinting that the "flat" part of flatMap is join (or concat for lists where the name derives from).

Now, it's possible for a single datatype to have multiple different binds. Mathematically, a Monad is actually the data type (or, really, the set of values that the monad consists of) along with the particular bind and return operations. Different operations lead to different (mathematical) Monads.

J. Abrahamson
  • 72,246
  • 9
  • 135
  • 180