8
votes

Where are the Applicative transformer classes? I wanted to use transformer classes for the applicative transformer stack in a previous answer, but they don't seem to exist.

The transformers package and many others are full of transformers that preserver Applicative structure, even when the underlying structure isn't a Monad.

A quick glance at transformers has Applicative instances for most of the transformers.

Applicative f => Applicative (Backwards f)
Applicative f => Applicative (Lift f)
Applicative (ContT r m)
Applicative m => Applicative (IdentityT m)
Applicative m => Applicative (ReaderT r m)
(Monoid w, Applicative m) => Applicative (WriterT w m)
(Applicative f, Applicative g) => Applicative (Compose f g)
(Applicative f, Applicative g) => Applicative (Product f g)

Only transformers for state and alternation (ExceptT and MaybeT) require an underlying monad for the Applicative instance.

(Functor m, Monad m) => Applicative (ExceptT e m)
(Functor m, Monad m) => Applicative (MaybeT m)
(Monoid w, Functor m, Monad m) => Applicative (RWST r w s m)
(Functor m, Monad m) => Applicative (StateT s m)

There's a class for Monad transformers. I can see how something could require this Monad constraint, since it can't be introduced elsewhere.

class MonadTrans t where
    lift :: (Monad m) => m a -> t m a

Where's the class for Applicative transformers?

class ApTrans t where
    liftAp :: (Applicative f) => f a -> t f a

Or just plain old transformers (though I can't imagine any laws for this)?

class Trans t where
    liftAny :: f a -> t f a

Due to the difference only in polymorphic constraints, these typeclasses have a strange variance pattern. Except for their laws, which have to consider unexpressible constraints, anything that is an instance of Trans should automatically be an instance of ApTrans and MonadTrans, and anything that's an instance of ApTrans should automatically be an instance of MonadTrans.

If we move on to the mtl library, the classes there are also incompatible with an Applicative transformer stack. All of the mtl classes I'm familiar with have a Monad constraint. For example, here's MonadReader

class Monad m => MonadReader r m | m -> r where
    -- | Retrieves the monad environment.
    ask   :: m r
    ask = reader id

    -- | Executes a computation in a modified environment.
    local :: (r -> r) -- ^ The function to modify the environment.
          -> m a      -- ^ @Reader@ to run in the modified environment.
          -> m a

    -- | Retrieves a function of the current environment.
    reader :: (r -> a) -- ^ The selector function to apply to the environment.
           -> m a
    reader f = do
      r <- ask
      return (f r)

What is the purpose of the Monad constraint? It makes MonadReader and the MonadReader instances for many of the above transformers incompatible with Applicative transformer stacks.

I would naively write something like

class Reader r m | m -> r where
    ask :: m r
    local :: (r -> r) -> m a -> m a

or even split local into a separate class.

class Reader r m | m -> r where
    ask :: m r

class (Reader r m) => Local r m | m -> r where
    local :: (r -> r) -> m a -> m a

local might be quite hard to use without a Monad instance. A more useful interface without the Monad constraint would be something like

class (Reader r m) => Local r m | m -> r where
    local :: m (r -> r) -> m a -> m a

Are there existing transformer classes somewhere that don't have the Monad constraint, or is there an actual need for yet another transformer class library?

2
This is worth reading about combining Applicatives in various ways. - AndrewC
@AndrewC Thanks, I wish I had read that before trying to answer the linked question. - Cirdec
I think there are applicative transformers that don't come from composing applicatives. For example, define monadic streams data MStream m a = MStream (a, MStream m a). Then MStream Identity is an Applicative, and for any Applicative m, MStream m is an Applicative, and there is an obvious lift :: m a -> MStream m a by infinite repetition. Yet, MStream m is not the composition of MStream and m! (Exercise, what is it?) - Turion
@Turion MStream m a is an Applicative regardless of what m is; m is a phantom type. It's the same as data Stream a = Stream a (Stream a). If you meant for the as to be wrapped in an m, then it's exactly the composition of the Applicatives Stream and m, Compose Stream m. That's what composing Applicatives does; it wraps each occurrence of the argument to the outer functor with the inner functor. - Cirdec
@Cirdec, I have a fundamental typo in my comment. I meant MStream m (a, MStream m a)!! - Turion

2 Answers

9
votes

Applicatives, unlike Monads, are closed under products and composition and thus don't need a special class of things like "transformers". Here's a small library:

data (*) f g x = P (f x) (g x)     deriving Functor
data C   f g x = C (f (g x))       deriving Functor

instance (Applicative f, Applicative g) => Applicative (f * g) where
  pure a = P (pure a) (pure a)
  P ff gf <*> P fx gx = P (ff <*> fx) (gf <*> gx)

instance (Applicative f, Applicative g) => Applicative (C f g) where
  pure = C . pure . pure
  C fgf <*> C fgx = C (liftA2 (<*>) fgf fgx)

Moreover, all monads are Applicatives so we ought to be able to reuse that code. Sadly, the lack of Applicative-Monad subtyping forces monadic code to be more exclusionary than needed and thus outlaws such code. It could have been rectified if all of these libraries asked for an (Applicative m, Monad m) constraint, but they do not. Furthermore, given how often you might otherwise have to write

(MonadReader m, Monad m) => ...

the Monad superclass constraint is convenient. I'm not sure it's completely necessary however.

5
votes

As J. Abrahamson said, Applicatives are closed under products and composition, so there's no need for dedicated transformer versions. However, there's also no need to roll your own Applicative product/composition types, because the Platform already has these:

I've found that the easier way to use these is with the GeneralizedNewtypeDeriving extension, because then you can just define types like these:

newtype MyType m a = MyType (Compose (Const m) (Reader m) a)
    deriving (Functor, Applicative)

-- Plus a bunch of utility definitions to hide the use of Compose and generally
-- keep you sane...

Another other useful tool in the Applicative toolset is the free applicative functor. I normally use Edward Kmett's free library's version, but it's easy to roll your own if you want fewer dependencies.

These definitions can also be useful (though I'd welcome suggestions on the naming scheme, particularly the "I/O" bit):

{-# LANGUAGE Rank2Types, TypeOperators #-}

import Control.Applicative
import Data.Functor.Compose

-- | A handy infix type synonym for 'Compose', which allows us to
-- stack 'Applicative's with less syntactic noise:
-- 
-- > type CalculationT s p f = Reader (Frame s p) :. Reader (Cell s p) :. f
-- > type Calculation s p = Calculation s p Identity
--
-- Note that 'Identity' and ':.' form something a type-level monoid
-- modulo @newtype@ equivalence.  The following isomorphisms hold:
--
-- > f :. Identity  ~=  Identity :. f  ~=  f
-- > f :. g :. h  ~=  (f :. g) :. h 
--
type f :. g = Compose f g
infixr :.

-- | Lift an action from the outer functor into the composite.
-- Alternative reading: append an 'Applicative' to the right of @f@.
liftO :: (Functor f, Applicative g) => f a -> (f :. g) a
liftO = Compose . fmap pure

-- | Lift an action from the inner functor into the composite.
-- Alternative reading: prepend an 'Applicative' to the left of @g@.
liftI :: Applicative f => g a -> (f :. g) a
liftI = Compose . pure

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @h :. g@.
hoistO :: (forall x. f x -> h x) -> (f :. g) a -> (h :. g) a
hoistO eta = Compose . eta . getCompose

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @f :. h@.
hoistI :: Functor f => (forall x. g x -> h x) -> (f :. g) a -> (f :. h) a
hoistI eta = Compose . fmap eta . getCompose