Reputation: 79
I just have a quick question about applicative functors to help me get a grasp on them. This is just stuff I am applying in ghci.
[(+3),((-) 3),(*3)] <*> [4]
[7,-1,12]
This makes sense to me. Basic application. But when trying:
[(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> [Just 4]
I am given a large amount of errors. I have somewhat of an understanding why; there are two data constructors ([]
and Maybe
) and the <*>
function only "peels back" one of them. What I'd like to assist my understanding is what exactly haskell is trying to do step by step until it fails, and how you could get around it and have this successfully compute to:
[(Just 7),(Just -1),(Just 12)]
Upvotes: 2
Views: 135
Reputation: 531165
You have two different Applicative
instances. It is true that
Just (* 3) <*> Just 4 == Just 12
but the []
instance just tries to apply each "function" in the first list to each value in the second, so you end up trying to apply
(Just (* 3)) (Just 4)
which is an error.
(More precisely, your list of Just
values just has the wrong type to act as the first argument for <*>
.)
Instead, you need to need to map <*>
over the first list.
> (<*>) <$> [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> [Just 4]
[Just 7,Just (-1),Just 12]
(Mapping a higher-order function over a list is how you usually get a list of wrapped functions in the first place. For example,
> :t [(+3), ((-) 3), (* 3)]
[(+3), ((-) 3), (* 3)] :: Num a => [a -> a]
> :t Just <$> [(+3), ((-) 3), (* 3)]
Just <$> [(+3), ((-) 3), (* 3)] :: Num a => [Maybe (a -> a)]
)
Data.Functor.Compose
mentioned in the comments is another option.
> import Data.Functor.Compose
> :t Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))]
Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))]
:: Num a => Compose [] Maybe (a -> a)
> Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> Compose [Just 4]
Compose [Just 12,Just (-1),Just 12]
> getCompose <$> Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> Compose [Just 4]
[Just 12,Just (-1),Just 12]
The definition of Compose
is very simple:
newtype Compose f g a = Compose { getCompose: f (g a) }
The magic is that as long as f
and g
are both (applicative) functors, then Compose f g
is also a (applicative) functor.
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose (fmap (fmap f) x)
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure x = Compose (pure (pure x))
Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)
In the Applicative
instance, you can see the same use of (<*>) <$> ...
that I used above.
Upvotes: 9