The wiki on www.haskell.org tells us the following about Applicative Transformers:
So where are applicative transformers? The answer is, that we do not ne
As usual, it's useful here to focus on the types to figure out what composition of applicative functors should mean.
If we write a
for the type of a particular pure value x
, that therefore has no side effects, then we can lift this pure value to a computation of the applicative functor f
using the pure
combinator. But by the same token, we can use the pure
function from g
's Applicative
instance to lift pure x
into the g
functor.
pure (pure x) :: g (f a)
Now g (f a)
is the type of computations that combine the effects of g
and the effects of f
. Looking at your tests, we notice that
test1 :: [String]
You have used only one effect in test1
, that is the non-determinism that the list instance of Applicative
gives you. Indeed, breaking it down:
"abc" :: String
((:) <$>) :: [Char] -> [String -> String]
((:) <$> "abc") :: [String -> String]
((:) <$> "abc" <*> ["pqr", "xyz"]) :: [String]
Now, if we want to compose the failure effect and the non-determinism effect, we would expect to construct a computation of type Maybe [a]
, or perhaps [Maybe a]
. It turns out that the two are equivalent, because applicative functors always commute.
Here's a computation of type [Maybe Char]
. It will non-deterministally return a Char
, but if it does, it might fail:
x1 = [Just 'a', Just 'b', Just 'c']
Likewise, here's a computation of type [Maybe String]
:
x2 = [Just "pqr", Just "xyz"]
Now we want to lift (:)
to this combined applicative functor. To do so, we have to lift it it twice:
pure (pure (:)) :: [Maybe (Char -> String -> String)]
Likewise, to apply it, we need to push this computation through both functors. We can therefore introduce a new combinator (<<*>>)
that does that:
(<<*>>) :: (Applicative f, Applicative f1) =>
f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
(<<*>>) = liftA2 (<*>)
Which now allows us to write:
pure (pure (:)) <<*>> x1 <<*>> x2
which you can check has the expected type.
But since applicative functors are closed under composition, [Maybe a]
is itself an applicative functor and so you may wish to be able to reuse pure
and (<*>)
. The Data.Functor.Compose
module of the transformers package shows you how to.