Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applicative instance for MaybeT m assumes Monad m

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?

like image 546
davidsd Avatar asked Mar 30 '14 21:03

davidsd


1 Answers

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

like image 94
Petr Avatar answered Nov 15 '22 08:11

Petr