Say I have a data type A
which is applicative. (For the sake of the example, we can assume A
is Identity
).
I now have a new data type that corresponds to the "transformation" from one A
to another:
data B a b = B (A a -> A b)
I want to define the trivial Applicative instance for (B a)
that produces a new transformation which applies both arguments of <*>
to its input and then uses the definition of <*> from the Applicative instance of A.
Formulating this is simple enough:
instance Applicative (B a) where
pure x = B $ const $ pure x
(B ftrans) <*> (B xtrans) = B fxtrans
where fxtrans inp = let fout = ftrans inp
xout = xtrans inp
in fout <*> xout
However, I have the feeling that there should be a straightforward pointfree way of writing this using the fact that (-> a)
is an Applicative Functor.
As a sample of what I have in mind, consider my definition of the corresponding Functor instance:
instance Functor (B a) where
fmap f (B xtrans) = B $ (fmap f) <$> xtrans
Is there a similarly simple way to define the Applicative instance?
One of the neat facts about Applicative
is that this class is closed under composition. You can get the following from Data.Functor.Compose
:
newtype Compose f g a = Compose { getCompose :: f (g a) }
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose fga) = Compose (fmap (fmap f) fga)
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure a = Compose (pure (pure a))
Compose f <*> Compose x = Compose $ (<*>) <$> f <*> x
The Applicative
instance for (->) a
, which you bring up, is this:
instance Applicative ((->) r) where
pure = const
ff <*> fa = \r -> let f = ff r
a = fa r
in f a
Now, let's expand Compose ff <*> Compose fa :: Compose ((->) (A a)) A b
(some steps skipped):
Compose ff <*> Compose fa
== Compose $ (<*>) <$> ff <*> fa
== Compose $ \r -> let f = ff r
a = fa r
in f <*> a
So what you're doing is effectively the composition of (->) (A a)
and A
.
This, probably?
(B ftrans) <*> (B xtrans) = B ((<*>) <$> ftrans <*> xtrans)
To piggy-back on Luis Casillas's answer: If B
is literally the data type you're working with, you can simply use Compose ((->) (A a)) A
instead. The data constructor would have type Compose :: (A a -> A b) -> Compose ((->) (A a)) A b
.
You can also use a type synonym: type B a = Compose ((->) (A a)) A
.
You can have a lot of fun smooshing functors together with Compose
, Product
, Sum
, and friends.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With