Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applicative transformer classes

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?

like image 327
Cirdec Avatar asked Sep 12 '14 00:09

Cirdec


2 Answers

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.

like image 174
J. Abrahamson Avatar answered Nov 16 '22 04:11

J. Abrahamson


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:

  • Data.Functor.Compose
  • Data.Functor.Product
  • Data.Functor.Constant
  • Data.Functor.Identity
  • Control.Applicative.Lift

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
like image 21
Luis Casillas Avatar answered Nov 16 '22 02:11

Luis Casillas