13

I've been using the Haxl monad (described here: http://www.reddit.com/r/haskell/comments/1le4y5/the_haxl_project_at_facebook_slides_from_my_talk), which has the interesting feature that <*> for its Applicative instance isn't the same as ap from Control.Monad. This is a key feature that allows it to do concurrent computations without blocking. For example, if hf and ha are long computations, then

let hf :: Haxl (a -> b) = ...
    ha :: Haxl a = ...
in do
  f <- hf
  a <- ha
  return (f a)

will do them sequentially, while

hf <*> ha

will do them in parallel and then combine the results.

I would like to be able to run computations in MaybeT Haxl, but the problem is that the Applicative instance for MaybeT m in the transformers package uses monadic bind:

instance (Functor m, Monad m) => Applicative (MaybeT m) where
    pure = return
    (<*>) = ap

Where ap = liftM2 id is from Control.Monad. This makes

let hmf :: MaybeT Haxl (a -> b) = ...
    hma :: MaybeT Haxl a = ...
in hmf <*> hma

run sequentially. It seems like a better instance would be more like

instance (Applicative m) => Applicative (MaybeT m) where
    pure = MaybeT . pure . Just
    MaybeT f <*> MaybeT x = MaybeT $ (<*>) <$> f <*> x

(Here, (<*>) on the right-hand side is for the Maybe monad, while the non-parenthesized <*> on the right-hand side is for m.) Note that the context is different -- the above instance assumes only Applicative m, while the instance in transformers assumes Functor m, Monad m.

My main question is practical: what should I do about this? Should I roll my own MaybeT monad transformer? Is there some way to get around the "Duplicate instance declarations" complaint that ghc gives me if I try to write the above?

I'd also like to know: is the current setup a design flaw in the transformers package? If not, why not?

Michael
  • 41,026
  • 70
  • 193
  • 341
davidsd
  • 771
  • 4
  • 18
  • 6
    Everyone tends to assume that superclass implementations match subclass implementations exactly. Haxl is violating this unwritten rule and thus can't play nicely with others. Transformers is also assuming you're making `Monad` transformers, thus its implementation. `Applicative` "transformers" have much nicer structure. – J. Abrahamson Mar 30 '14 at 22:07
  • @J.Abrahamson, this is not a rule, written or otherwise. Nor is this the only case where `<*>` is better, in some significant way, than `ap`. It seems to me that `MaybeT` is in the wrong here. – dfeuer Jan 03 '15 at 18:44
  • After [AMP](https://www.haskell.org/haskellwiki/Functor-Applicative-Monad_Proposal) is wide-spread as part of GHC 7.10, we could rewrite `Applicative (MaybeT m)` definition. currently `Applicative` isn't a superclass of `Monad`, so you might break lots of packages. Also I see surprisingly many *Foo is instance of Monad but not of Applicative* warnings still. Would suggest to write your own `MaybeT` for now - or maybe make a PR to `transformers`?. – phadej Jan 03 '15 at 19:24
  • @dfeuer It tends to be an unwritten rule. People frequently assume you can just switch out do syntax and `(<*>)` syntax as convenient and pretty. Once AMP lands this will be even stronger, but it'll also be pretty easy to switch out this definition with the Pplicative only one. – J. Abrahamson Jan 03 '15 at 20:47
  • 1
    Actually it's a written rule – it's right there in the [Control.Applicative documentation](http://hackage.haskell.org/package/base-4.7.0.2/docs/Control-Applicative.html) – Ben Millwood Jan 04 '15 at 19:34
  • @BenMillwood I agree with dfeuer. The most natural way of reading the rules you mention is as extensional equalities (i.e. same results for the same arguments), which do not forbid an `ap` that happens to be less efficient than `(<*>)` (cf. the remarks on `(*>)` a few lines above in the documentation, as well as [this GHC ticket](https://ghc.haskell.org/trac/ghc/ticket/11128)). Thankfully, the AMP is now a reality and `MaybeT` was fixed in the way phadej alludes to. Still, this is a neat example of why it is better not to instantiate `Applicative` in terms of `Monad`. – duplode Oct 17 '16 at 16:10

1 Answers1

7

The trick is that (unlike monads) applicative functors are composable, so you don't need (applicative) transformers such as MaybeT. Instead, you can use Compose to combine two applicative functors together:

import Control.Applicative
import Data.Functor.Compose

type HaxlM = Compose Haxl Maybe

-- if you prefer to have a function for constructing values:
haxlM :: Haxl (Maybe a) -> HaxlM a
haxlM = Compose

The composition is always a proper instance of Applicative and use only the Applicative instance of their components. For example:

test = getZipList . getCompose
       $ (+) <$> Compose (ZipList [Just 1,  Nothing, Just 3])
             <*> Compose (ZipList [Nothing, Just 20, Just 30])

produces [Nothing,Nothing,Just 33].

Petr
  • 62,528
  • 13
  • 153
  • 317
  • 1
    Ok, cool. But in this case I couldn't also use monad operations, right? (A general Haxl computation uses both applicative and monad to represent different data flows.) Is there some way to get the best of both worlds (i.e. a HaxlM monad with ap != <*>, without writing one from scratch)? – davidsd Mar 31 '14 at 05:44
  • 1
    @davidsd I guess no. You could perhaps use `FlexibleInstances`/`FlexibleContexts` to create a monad instance for `Compose Haxl Maybe`, but then it's probably better to rather create the `HaxlM` monad from scratch. – Petr Mar 31 '14 at 08:07
  • 2
    As long as we're on this path, it's worth noting that `Applicative` products are also `Applicative`. Same with `Applicative` sums so long as there's a natural transformation which lets bias work: http://comonad.com/reader/2012/abstracting-with-applicatives/ – J. Abrahamson Mar 31 '14 at 15:09