Functor
The basis of things to come
[This article was expanded on 2020-04-06.]
The first place to look for an introduction to any structure is Hackage, which covers Functor
pretty well. Consider this article a commentary on that page.
There we are told Functors are:
uniform action over a parameterized type, generalizing the map function on lists.
The class provides a single higher order method for converting the parameter type of the functor.
If the transformer function, f
is of type Foo->Bar
then fmap f
will convert Baz Foo
values into Baz Bar
values.
Often the <$>
operator is used. It is but an alias for fmap
.
Functors are really quite primal and I would advise anyone starting out with Haskell prioritising getting used using them, stacks of them. If you have an IO
action that returns a Maybe String
but you need a Maybe T.Text
then this should be quite natural (bearing in mind that, like many useful types, IO
and Maybe
are functors):
Of course fmap T.pack
(in this context) is of type
which is just a function we can apply as an argument to fmap
:
and so,
but fmap
and <$>
are the same function (in the standard Prelude
anyway) so this is equivalent to:
[Notice the way that whole explanation of stacked functors took place entirely in the land of types — this is a central feature of Haskell that every Haskell program or fragment has a dual life: a static life in types and a dynamic life at runtime.]
The above pattern is so pervasive that it is worth getting familiar with, especially in an IO
context. f <$> action
(or fmap f action
) applies some arbitrary function f
to the output of the IO
action
.
But this is bur a single pattern. Functors can be applied pretty much anywhere.
Other Operators
Functor provides some useful auxiliary operators and functions.
(<$)
is equivalent to this
forcing the output of s
to a constant value:
-- | uses `getLine` to read a line from stdin and discards it
discardLine :: IO ()
discardLine = () <$ getLine
($>)
This operator
is the same operator as <$
, but with its arguments flipped:
so we could have written above:
-- | uses `getLine` to read a line from stdin and discards it
discardLine :: IO ()
discardLine = getLine $> ()
(<&>)
This operator,
is a flipped version of ($>)
so we can say
import Data.Char (toUpper)
-- | read string from stdin, mapping each char to upper case
getUpper :: IO String
getUpper = readLine <&> map toUpper
void
This is just a special case of <$
(or $>
) that force the output of the functor to the unit type ()
.
Above, e could have written:
-- | uses `getLine` to read a line from stdin and discards it
discardLine :: IO ()
discardLine = void getLine
Best is yet to come
In [the notes for ($)
on the Hackage Functor
page notes the symmetries between the function application operator ($)
and (<$>)
.
This is not accidental and totally in keeping with the way it is used, including the examples at the top of this article.
Just as ($)
can be used to transform the output of a function, so (<$>)
can be used to transform the output of a Functor.
import Data.Char (toLower)
import System.Environment (getEnv)
data Env = Env { envLabel :: String }
prjLabel :: Env -> String
getLabel :: String -> IO String
prjLabel env = map toLower $ envLabel env
getLabel nme = map toLower <$> getEnv nme
But hold on a minute, aren’t we getting ahead of ourselves, getEnv
is an IO
, a famous monad, not a Functor? But all monads are applicatives and all applicatives are functors, so monads are functors of course.
Everything we are saying about functors is true of applicatives (and monads), and everything we say about applicatives will be true of monads. This compositional understanding is important to understanding the big picture, and as we can see it provides an accessible place to start.
Identities
Functors come from abstract algebra and, as we are reminded at the top of the top Functor Hackage page, where they are characterised by the following identities.
That these simple identities (along with those for the other structures) can give rise to all of this is truly a wonderous thing!
Got an issue with any of this? Please share or drop me a line (see below).