In our lecture on testing, we visited with the humble functor.
class Functor f where
fmap :: (a -> b) -> f a -> f b
But how good is our intuition for what a functor is?
Please tell me what the following computes:
fmap (+1) [1,2,3]
Please tell me what the following computes:
import Data.Char
fmap toUpper "qwertyuiop"
Let's avoid name clash with the standard Functor
class:
class MyFunctor f where
myfmap :: (a -> b) -> f a -> f b
Please write a MyFunctor
instance for Maybe
.
You have 2 minutes.
Let's avoid name clash with the standard Functor
class:
class MyFunctor f where
myfmap :: (a -> b) -> f a -> f b
Here is a MyFunctor
instance for Maybe
.
instance MyFunctor Maybe where
myfmap _ Nothing = Nothing
myfmap f (Just a) = Just (f a)
Please dictate to me a MyFunctor
instance for Identity
.
newtype Identity a = Identity a
(You can find this type in Data.Functor.Identity
.)
Suppose we think of a functor as a container.
What do we know about what a functor does to the things inside the container?
How about the structure of the container itself?
You may not have come across the "tupling" operator yet:
(,) :: a -> b -> (a, b)
Given two arguments, it returns a pair consisting of those arguments.
Since (,)
is a operator, we can surround it in parentheses to use it as a function.
ghci> :type (,) 'X' True
(,) 'X' True :: (Char, Bool)
In typical Haskell fashion, we can partially apply the function to yield another function:
ghci> :type (,) 'X'
(,) 'X' :: b -> (Char, b)
OK, we can use (,)
in prefix position as a function.
We can also write (,)
as a type constructor.
foo :: b -> (,) Char b
foo b = (,) 'X' b
This means exactly the same thing as the following signature:
foo :: b -> (Char, b)
What should a MyFunctor
instance for pairs look like?
instance MyFunctor ((,) a) where
{- ... -}
Remember, for a type to be an instance of MyFunctor
, we need one free type parameter:
class MyFunctor f where
myfmap :: (a -> b) -> f a -> f b
By convention, we choose the second element of the pair to be free in our MyFunctor
instance.
What should myfmap
look like?
How useful is our functors-as-containers metaphor?
Recall the enigmatic Identity
type.
newtype Identity a = Identity a
Since this is a newtype
, it has no runtime representation.
So strictly speaking, it's not really a container:
I had a purpose in talking about (,)
as a prefix operator.
We can do the same with the (->)
operator for describing functions.
foo :: (->) Char Bool
foo c = isUpper c
Since we were able to write a MyFunctor
instance for pairs:
instance MyFunctor ((,) a) where
myfmap f (a, b) = (a, f b)
Can we do the same for functions?
instance MyFunctor ((,) a) where
myfmap f (a, b) = (a, f b)
Anyone want to take a crack at this?
instance MyFunctor ((->) a) where
{- ... -}
A definition wasn't too hard to come up with:
instance MyFunctor ((->) a) where
myfmap f g = \x -> f (g x)
But what does this mean?
We already touched on functors in the context of IO
.
readFile "/etc/passwd"
This executes a real-world action, and gives us back a String
.
(length . lines) `fmap` readFile "/etc/passwd"
This executes the same real-world action, and gives us back...what?
Mapping the identity function has no effect on the result.
fmap id === id
Mapping the composition of two functions is the same as composing the mapping of the same functions.
fmap (g . h) = (fmap g) . (fmap h)
The standard way of writing the type of fmap
can be a bit obscure:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Functions in Haskell are always curried, so fmap
"is really" a function of one argument that returns another function.
Let's add parentheses to make this clear.
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
It lifts its first argument from being a normal function to one that operates in this universe where everything is shrouded in f
.
You'll come across the damn things everywhere in Haskell.
Next to Monoid
, Functor
is one of the simplest abstractions in Haskell.
The fact that (->) a
is a Functor
(but not a container) is invaluable:
f
a nameclass Functor f where
fmap :: (a -> b) -> (f a -> f b)
So containers are only a training-wheels metaphor.
It'll still be helpful to generically refer to this f
thing by a name.
We'll call it a context.
The []
functor:
The (->) a
functor:
a
(a "read-only environment").The IO
functor:
Here's our next step up the expressive ladder.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The pure
function takes a value and lifts it into our new context.
What about (<*>)
?
Consider its similarity to both fmap
and ($)
.
(<*>) :: f (a -> b) -> f a -> f b
fmap :: (a -> b) -> f a -> f b
($) :: (a -> b) -> a -> b
They're clearly all related!
($)
is function application
fmap
is function application lifted to functors
(<*>)
is function application lifted to functors, but where the initial function is wrapped in our context f
too
This is the origin of the name "applicative".
Just as with monoids and functors, instances of Applicative
must follow some laws.
In this case, there are 4 laws.
You can refer to them at the Typeclassopedia if you're interested.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
This will give us the flavour of the Applicative
class.
instance Applicative Maybe where
pure x = Just x
What should the implementation of (<*>)
look like?
-- (<*>) :: f (a -> b) -> f a -> f b
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Take 2 minutes to write your own.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
This will give us the flavour of the Applicative
class.
instance Applicative Maybe where
pure x = Just x
What should the implementation of (<*>)
look like?
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Just f <*> Just x = Just (f x)
_ <*> _ = Nothing
If you want to gain some good understanding of Applicative
:
Identity
, and (->) a
Bonus material:
newtype MyConst a b = MyConst a
Write Functor
and Applicative
instances for the MyConst
type above.
Here's a tiny Applicative
-powered parser for URL-encoded bytes such as %27
.
import Control.Applicative
import Data.Char (chr)
import Numeric (readHex)
import Text.Parsec (char, hexDigit)
import Text.Parsec.String (Parser)
hexChar :: Parser Char
hexChar = char '%' *> (combo <$> hexDigit <*> hexDigit)
where combo a b = case readHex [a,b] of
[(n,"")] -> chr n
_ -> error "wat"
This depends on:
-- Sequence two actions, discarding the result of the first.
(*>) :: Applicative f => f a -> f b -> f b
-- You'll see this everywhere.
(<$>) = fmap
Let's parse an entire application/x-www-form-urlencoded
string.
They look like this:
name=bryan+o%27sullivan&city=san+francisco
Top-level parser:
query = pair `sepBy` char '&'
We'll revisit sepBy
in a moment.
-- Zero or more elements, separated by a separator.
sepBy :: Alternative f => f a -> f sep -> f [a]
First, we must grok Alternative
.
Alternative
classThis class combines monoids with applicative functors:
class Applicative f => Alternative f where
empty :: f a
(<|>) :: f a -> f a -> f a
empty
corresponds to mempty
.
(<|>)
corresponds to mappend
/(<>)
.
Notice that
-- Zero or more elements, separated by a separator.
sepBy :: Alternative f => f a -> f sep -> f [a]
sepBy p sep = sepBy1 p sep <|> pure []
-- One or more elements, separated by a separator.
sepBy1 :: Alternative f => f a -> f sep -> f [a]
sepBy1 p sep = (:) <$> p <*> many (sep *> p)
many :: Alternative f => f a -> f [a]
pair :: Parser (String, Maybe String)
pair = (,) <$> many1 urlChar
<*> optional (char '=' *> many urlChar)
urlChar = oneOf urlBaseChars
<|> hexChar
<|> ' ' <$ char '+'
New combinators:
optional :: Alternative f => f a -> f (Maybe a)
-- Replace all locations in the input with the same value.
(<$) :: Functor f => a -> f b -> f a
This code is amazingly compact and readable!
query = pair `sepBy` char '&'
pair :: Parser (String, Maybe String)
pair = (,) <$> many1 urlChar
<*> optional (char '=' *> many urlChar)
urlChar = oneOf urlBaseChars
<|> hexChar
<|> ' ' <$ char '+'
hexChar :: Parser Char
hexChar = char '%' *> (eval <$> hexDigit <*> hexDigit)
where eval a b = case readHex [a,b] of
[(n,"")] -> chr n
_ -> error "wat"
urlBaseChars = ['a'..'z']++['A'..'Z']++['0'..'9']++"$-_.!*'(),"
Monad
Every Applicative
is a Functor
.
And every Monad
is an Applicative
.
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
return
is the same as pure
.
What about (>>=)
("bind")?
Where does it fit into our mental universe?
There's a standard function named (=<<)
which is exactly (>>=)
, but with its arguments flipped.
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
Why should we care?
Remember this?
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
fmap :: Functor f => (a -> b) -> f a -> f b
($) :: (a -> b) -> a -> b
These are all different ways of applying a function to a value.
A small change: add (=<<)
.
(=<<) :: Monad m => (a -> m b) -> m a -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
fmap :: Functor f => (a -> b) -> f a -> f b
($) :: (a -> b) -> a -> b
So really, the (>>=)
operator is "just" another application operator, but its flipped argument order obscures this.
Functor
and Applicative
cannot doConsider application with functors and applicative functors:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
fmap :: Functor f => (a -> b) -> f a -> f b
How do we ensure that they can only operate on the elements of a container?
Their function argument cannot see or influence f
at all.
As a result, they must be oblivious to the enclosing structure of the container (or computational context, or whatever).
Applicative
to Monad
The key to Monad
is that the a -> m b
function can take a normal Haskell value and use it to decide what m b
to give back:
(=<<) :: Monad m => (a -> m b) -> m a -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Compared to Applicative
, Monad
is both more powerful and harder to reason about.
Applicative
and Functor
are related. Monad
is independent of the other two due to accidents of history.
This will change with GHC 7.10.
Always try to use the least powerful abstraction you can.
Use Applicative
in preference to Monad
.
Use Functor
in preference to Applicative
.
(Unless you can't, of course.)
Why?
The less powerful the abstraction, the easier its behaviour is to reason about.
It becomes harder for you and your users to perform foot-shooting.