cmal
cmal

Reputation: 2222

How to define the `pure` function on `Applicative`?

The book Haskell Programming from First Principles has an exercise which asks me to instance Applicative on Constant a.

I tried this:

newtype Constant a b =
  Constant { getConstant :: a }
  deriving (Eq, Ord, Show)

instance Functor (Constant a) where
  fmap f (Constant x) = Constant x

instance Monoid a => Applicative (Constant a) where
  pure x = Constant { getConstant = x }
  Constant { getConstant = x } <*> Constant { getConstant = y } =
    Constant { getConstant = x <> y }

This does not compile. I sneaked the source code of Data.Functor.Constant and it has the following definition:

pure _ = Constant mempty

I do not really understand Applicative and its pure function, even after read the Chapter of Applicative in this book.

I know this may be a big question, but can someone explain what the pure does, and why they just define pure use some mempty on a Monoid? And why this one:

pure x = Constant { getConstant = x }

does not compile? I thought the pure just transform some value of type a to some value of type f a, and in this example f is just Constant. Why is it different from the pure of Identity a?

Upvotes: 3

Views: 543

Answers (2)

Will Ness
Will Ness

Reputation: 71070

Look at the type. (the eternal advice!)

data Const a b = MkConst a

For example,

> MkConst 1 :: Const Int Bool
> MkConst 1 :: Const Int Float
> MkConst 1 :: Const Int [(Integer, Maybe [()])]

Next,

instance Functor (Const a) where
  fmap f (MkConst x) = MkConst x       

What? .... but wait; it's actually

instance Functor (Const a) where
 -- fmap :: (b -> c) -> (Const   a b) -> (Const   a c)
    fmap    f           (MkConst x  ) =  (MkConst x  )

Now it's much clearer, isn't it? Const a is a Functor. Under the container paradigm, in Functor f => f b the f is the shell and b is the stuff inside. And f ~ Const a is the empty shell. With a whiff of what could have been there, but isn't. But that's just literature. Things do not have to make sense under everyday paradigms and metaphors. The only thing that matters is that - the - types - fit.

Don't try making it all out in your head; put it down on paper. This is a very important advice that I wish I got much earlier, myself. Don't skip over things; repeat stuff as many times as need be.

Similarly,

instance Monoid a => Applicative (Const a) where
  -- pure :: Applicative f         => b ->  f         b
  -- pure :: Applicative (Const a) => b -> (Const   a b)
  pure                                x =  (MkConst x  )

Wait, what? Is x :: a, or is it x :: b? Therein lies the rub. It is the latter, but you make it be the former.

Const is the empty shell, remember? Putting x in there still leaves it empty, even if it records the attempt. In other words the x itself is ignored, only its type comes into play:

  -- pure :: (Monoid a, Applicative (Const a)) => 
  --          b -> (Const   a      b)
  pure        x =  (MkConst mempty  )

(I'm using different names for the type and the data constructor here, to remove ambiguity, which can be confusing at first).

Upvotes: 0

Raveline
Raveline

Reputation: 2678

Your main trouble here is with kinds. I'm going to try and give you an explanation on why your code doesn't work, but for more details I recommend reading this excellent post on kinds.

Consider the first line returned when you ask for information about Applicative in ghci:

λ> :info Applicative
class Functor f => Applicative (f :: * -> *) where

See that f :: * -> * bit ? It tells you the expected kind. Know nothing about kinds ? I'll give you the simplest exaplanation I can. Everytime you add a parametric type, you are basically telling you need "another type to build the type".

For instance, when you say Maybe a, you say "To have a Maybe, I need an a". Or when you write Either a b, you say "My Either type depends on type a and type b". You can get information about kind by using :kind or :k for short in ghci. Consider:

λ> :kind Bool
Bool :: *

λ> :kind Maybe
Maybe :: * -> *

λ> :kind Either
Either :: * -> * -> *

Each "*" represents a type. Bool is a simple type. Maybe all alone expects another type. Either all alone expects yet another type. Note the difference when I type:

λ> :kind Maybe Bool
Maybe Bool :: *

And now consider:

λ> :kind Either Bool
Either Bool :: * -> *

See this * -> * kind ? It's exactly the one we've seeen when the asked info about Applicative. (It's also the same kind expect for Functor and Monad).

This means that the typeclass will only operate on the latest parametric type. It's true for Either also. As you can see:

λ> let fun = (++ " !")
λ> fun <$> Left "Oops"
Left "Oops"

This does nothing, because the Functor for Either is not a functor on both types of either: it's a functor only on its last type (the b of Either a b). Using a simple Functor and fmap (or here the infix version <$>), I can only operate on the b of Either a b, which is why this one will work:

λ> fun <$> Right "Oops"
Right "Oops !"

Now, back to what you are trying to do. You have a newtype Constant a b, so Constant has kind * -> * -> *. And let's now look at the the second line from :info Applicative this time, that will give us the signature for pure:

class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a

Now always take into account this: the a in the signature of pure is not the a of Constant a b. Worse than that, in this very example, the a in pure is the b of your Constant. Because if you take into account kindness, and if you specialize this signature you get:

  pure :: b -> Constant a b

But that is not what you are doing, isn't it ? What is store in your newtype is the type a. And you're trying to put inside the type b. Since a and b can be different, it won't work.

As for "the big question" of "what pure does", that is indeed quite a question, and I'll give you the beginning of an answer.

pure is a way of making a naive value a enter an Applicative f. As the signature says: a -> f a. That doesn't help ? Ok, consider your Applicative as "a context" (I use a very general word, because applicatives are a very general notion). "Context" could be: I'm working in a world where things could fail (that is Maybe). It could be: I'm working in a world where there are many answers to one question (that is List or []). It can be many, many things - in your example, it is a context where nothing is ever computed and a constant will always be returned. The issue is that, in your example, it is impossible to "guess" what is the constant.

As we've seen the constant (your context) is not the value. It is not the a of pure. It is a part of the f. This is why the implementation uses Monoid and mempty: you need a way of getting a default context, and Monoids have always a default value available.

Finally, Applicative are hard. So it's perfectly normal not to understand them immediately. Focus on reading the types, trying to understand what the compiler is telling you, and it will get easier. Reread the chapter slowly, take your time.

Upvotes: 6

Related Questions