6

In Haskell, is there any alias for (liftM . liftM), (liftM . liftM . liftM) etc?

So that I don't have to be so verbose, e.g.:

(liftM . liftM) (+ 1) [Just 1,  Just 2]  = [Just 2, Just 3]
(liftM2 . liftM2) (+) [Just 1] [Just 2]  = [Just 3]
Lay González
  • 2,901
  • 21
  • 41
  • 3
    I believe this is precisely what [monad transformers](http://en.wikibooks.org/wiki/Haskell/Monad_transformers) are supposed to be all about, though for a simple use case like this it's probably better to just create aliases to your functions locally on your own, like `myMonadicAdd = (liftM . liftM ) (+)` since the use of nested monads will be specific to your application. – ely Feb 23 '15 at 02:18
  • 1
    `liftM . liftM` has a type incompatible with monad transformers, so if you find yourself needing this function *a lot*, then you should consider refactoring your code to use a monad transformer. If this particular example is your actual use case, this may not be good advice, because `ListT Maybe a` and `[Maybe a]` are not the same thing. – user2407038 Feb 23 '15 at 02:58
  • @user2407038 It's not my actual use case, but I thought it being very simple, it would be a good example for developing intuition. Then I tried to use `ListT Maybe a` and found out that the types indeed don't match, too bad. – Lay González Feb 23 '15 at 03:02
  • I wonder why we have liftIO, but not a general solution that does all the necessary lifts that works with any monad. @Mr.F little mistake: `(liftM . liftM) (+)` is a type error, liftM2 is needed for (+) – Lay González Feb 23 '15 at 03:07
  • 1
    I looks like you want a way to lift to an arbitrary depth. Unfortunately, there is no straight forward way to implement this, without resorting to something like Template Haskell. While having to occasionally define `fmap2`, is a bit annoying, in practice it is so rarely needed and so easily defined that they haven't included an alias for it. Here is a blog post discussing this: https://byorgey.wordpress.com/2007/08/16/mapping-over-a-nested-functor/ – danem Feb 23 '15 at 03:09
  • @LayGonzález Yes, the typo was just that I meant `(+1)` as in your `liftM . liftM` example. It was too late to edit the comment when I realized I left off the `1`. I wasn't going for the `liftM2` example. – ely Feb 23 '15 at 03:11
  • 2
    @user2407038 `liftM2.liftM2` works because in these cases `liftM2` = `liftA2` and Applicative functors _do_ compose (unlike monads in general). – AndrewC Feb 23 '15 at 13:51
  • @AndrewC if `<$> == liftA`, is there an operand, say `<$$>`, that `<$$> == liftA.liftA` ? – Lay González Feb 23 '15 at 18:55
  • @LayGonzález I like that idea - and you've inspired me to write an answer. – AndrewC Feb 24 '15 at 04:35

1 Answers1

7

There isn't such a thing as that in base, but well done for asking the most entertaining question for me on Stack Overflow for some time.

Functors and Applicative functors are closed under composition (which certainly isn't the case in general for monads, thus the need for monad transformers), which is why liftA2.liftA2 works here, and liftM2 is usually just liftA2, especially now Applicative is becoming a superclass of Monad.

Digression:

You can use the composition newtype in Data.Functor.Compose package to compose an Applicative, but you can make new applicatives out of old ones in other ways too - I strongly recommend Gershom Bazerman's post "Abstracting with Applicatives" in the Comonad Reader for folk that want to understand how beautiful a combined Applicative structure is compared to a monad transformer stack - I'm now always on the lookout for making things just Applicative rather than monadic where I can get the functionality I need. Often I can use Applicative to combine all the input stuff into a value I want to output then pipe it directly where i t's going using >>=.

Your functions and operators

Of course there's nothing stopping you defining your own functions:

liftliftA2 :: (Applicative f, Applicative g) =>
              (a -> b -> c) -> f (g a) -> f (g b) -> f (g c)
liftliftA2 = liftA2.liftA2

but it's not much shorter than liftA2.liftA2.

I love your idea to make nested Applicative operators, though, but will switch to increasing the angle brackets rather than repeating the internal operator because <**> clashes with (<**>) = flip (<*>) in Control.Applicative, and it's more logical.

import Control.Applicative

(<<$>>) :: (Functor f, Functor g) => 
           (a -> b) -> f (g a) -> f (g b)
(<<$>>) = fmap.fmap

(<<*>>) :: (Functor m, Applicative m, Applicative n) =>
            m (n (a -> b)) -> m (n a) -> m (n b)
mnf <<*>> mna = (<*>) <$> mnf <*> mna

giving

ghci> (+) <<$>> [Just 5] <<*>> [Just 7,Just 10]
[Just 12,Just 15]

and of course you can keep going:

(<<<$>>>) :: (Functor f, Functor g, Functor h) => 
             (a -> b) -> f (g (h a)) -> f (g (h b))
(<<<$>>>) = fmap.fmap.fmap

(<<<*>>>) :: (Functor l,Functor m, Applicative l, Applicative m, Applicative n) =>
             l (m (n (a -> b))) -> l (m (n a)) -> l (m (n b))
lmnf <<<*>>> lmna = (<*>) <<$>> lmnf <<*>> lmna

Which lets you do the apparently improbable

ghci> subtract <<<$>>> Right [Just 5,Nothing,Just 10] <<<*>>> Right [Just 100,Just 20]
Right [Just 95,Just 15,Nothing,Nothing,Just 90,Just 10]

but then again, as Gershom Bazerman's article demonstrates, you may well want to nest Applicatives, just as deeply as you might want to nest Monads.

AndrewC
  • 32,300
  • 7
  • 79
  • 115