Quoting a comment:
MaybeT
's (<*>)
is different from Compose m Maybe
Indeed:
ghci> import Data.Functor.Compose
ghci> import Control.Monad.Trans.Maybe
ghci> f = MaybeT [Nothing] :: MaybeT [] (Int -> Int)
ghci> a = MaybeT [Just 1, Just 2, Nothing] :: MaybeT [] Int
ghci> f <*> a
MaybeT [Nothing]
ghci> Compose (runMaybeT f) <*> Compose (runMaybeT a)
Compose [Nothing,Nothing,Nothing]
As for how they differ, (<*>)
for Compose
is defined as:
Compose u <*> Compose v = Compose (liftA2 (<*>) u v)
If the two applicative functors being composed are also monads (as []
and Maybe
are), we can express that in terms of their monad instances:
Compose (liftA2 (<*>) u v)
Compose $ do
ui <- u -- "i" is for "inner"
vi <- v
return (ui <*> vi)
Compose $ do
ui <- u
vi <- v
return $ do
f <- ui
a <- vi
return (f a)
Which, with the inner functor being Maybe
, becomes:
Compose $ do
ui <- u
vi <- v
return $ do
f <- ui
a <- vi
Just (f a)
Whereas (<*>)
for MaybeT
is
mf <*> mx = MaybeT $ do
mb_f <- runMaybeT mf
case mb_f of
Nothing -> return Nothing
Just f -> do
mb_x <- runMaybeT mx
case mb_x of
Nothing -> return Nothing
Just x -> return (Just (f x))
Let's rephrase the right-hand side in a style closer to the one above:
MaybeT u <*> MaybeT v = MaybeT $ do
ui <- u
case ui of
Nothing -> return Nothing
Just f -> do
vi <- v
case vi of
Nothing -> return Nothing
Just x -> return (Just (f x))
(<*>)
for Compose
runs the outer effects before the inner ones, and so in your example you get the usual list (<*>)
at the outer level, obtaining a list of 1 * 3 = 3 elements. (<*>)
for MaybeT
, however, runs the effects of its first argument (both the outer and the inner Maybe
) before moving on to the second one, and that only if a Just
is obtained, and so the Nothing
lying in f
in your example gives rise to merely another Nothing
, resulting in [Nothing]
overall.