Reputation: 2011
I am learning about Applicative Functors and the pure function has the following type declaration:
pure :: a -> f a
I understand that the pure function takes a value of any type and returns an applicative value with that value inside it. So if the applicative instance was Maybe
, pure 3
would give Just 3
.
However, what happens when you apply pure to a value that's already inside an applicative value? E.g. what happens if you do something like pure Just 3
?
Upvotes: 3
Views: 1384
Reputation: 8467
What happens when you apply pure to a value that's already inside an applicative value?
It just gets wrapped in an additional layer. The end result is an applicative value nested inside another applicative value.
E.g. what happens if you do something like
pure Just 3
?
This is an interesting question, although probably not for the reasons you meant. The important point here is to distinguish pure (Just 3)
— which was probably what you meant — from pure Just 3 = (pure Just) 3
, which is what you wrote. This gives two scenarios:
pure (Just 3)
simply applies pure
to the value Just 3
, which — as I discussed above — gives Just (Just 3)
, which is a nested applicative value.(pure Just) 3
is an interesting case. Recall the types of pure
and Just
:
pure :: Applicative f => a -> f a
Just :: a -> Maybe a
-- so:
pure Just :: Applicative f => f (a -> Maybe a)
In other words, pure Just
takes the function Just
and wraps it inside an applicative value.
Next, we want to take pure Just
and apply it to a value 3
. But we can’t do this, since f (a -> Maybe a)
is a value, not a function! So (pure Just) 3
should result in a type error.
…except it turns out to typecheck just fine! So we’re missing something. In this case, it turns out there is an applicative instance for functions:
instance Applicative ((->) r) where
pure x = \r -> x
(<*>) = _irrelevant_here
The syntax is a bit funny, but it basically means that r -> ...
is an applicative. This particular instance is known as the Reader monad, and it’s very widely used. (For more about this particular data type, see e.g. here or here.) The idea is that r -> a
can compute an a
given an r
input; in this case, pure x
creates a function which ignores its input and returns x
always, and f <*> x
feeds the r
input into both f
and x
, then combines the two. In this case, we’re only using pure
, so it’s easy to evaluate (pure Just) 3
by hand:
(pure Just) 3
= (\r -> Just) 3
= Just
So the idea here is that pure
wraps Just
in an applicative value, which in this case happens to be a function; then, we apply this function to 3
, which gets rid of the wrapper to reveal the original Just
.
Upvotes: 10
Reputation: 16224
First of all, pure has the type:
pure :: Applicative f => a -> f a
To make things simpler, think of the kind of f
:k f
f :: * -> *
and the kind of a
is *
then the type of a
, is just a
, any a
, the most polymorphic of all (but with kind *
remember). So you don't really care the value of a, you just have a restriction, and that's the typeclass
Applicative, and the kind of f
(remember * -> *
)
so in this case:
gchi> pure 3 :: Maybe Int
ghci> Just 3
here f
is Maybe
and a
is 3
In the same way
gchi> pure $ Just 3 :: Maybe (Maybe Int)
gchi> Just (Just 3)
here f
is again Maybe
and a
is Just 3
and you can play a little changing the type to pure:
gchi> pure 3 :: [Double]
ghci> [3.0]
here, f
is [], and a
is 3
same way
ghci> pure [3] :: [[Double]]
ghci> [[3.0]]
finally here, f
again, is []
and a
is [3]
Upvotes: 5