What Functors Can Do For You
One of the major aspects of Functional Programming, its lack of side effects, can be one of its major stumbling blocks. Sure, taking a value as a parameter and using it without creating a variable is a simple matter for basic types, but what of more complicated structures? How does one go about unwrapping a structure, operating on its value, and re-wrapping the value in said structure without losing it’s data? The answer is by using a convoluted set of functions that take way too many arguments just to preserve the state. Or, you could use a functor.
So I Overload Operator()?
No, we aren’t talking about C++ Classes with an
operator() implementation to make it look like a function. Functors in Haskell are a completely different animal. The easiest way to explain it is to look at an example. By way of example, let’s take a look at the
data Maybe a = Nothing | Just a deriving (Eq, Ord)
As you can see,
Maybe has two type constructors:
Maybe is used to represent a calculation that may have failed. If the calculation failed,
Nothing is returned. If it succeeded,
Just whatever is returned. This is all fine and good, but
Just whatever isn’t the same thing as
whatever; it is encapsulated in a
maybe, and must be extracted if it is to be used.
How do you do that? Well, first you have to account for a
Nothing value. Assuming you have a
Just, you have to extract it, probably using a new function. Then you have to use the value. Afterwards, you probably want to put it back into the
Maybe. Sounds like a lot of work, right? Let’s take a look at what
Functor has to offer us:
class Functor f where fmap :: (a -> b) -> f a -> f b
So, a functor defines 1 function (actually it defines more, but they have default implementations and can be ignored in 99% of cases)
fmap, which takes a function that takes 1 argument of type
a and returns a result of type
Functor a, and returns a
Functor b. Basically, this amounts to fmap takes a function, calls it on the passed in functor, and returns a functor with the resulting value.
Maybe This Is Helpful?
Let’s take a look at how this works with
Maybe. Here is
instance Functor Maybe where fmap _ Nothing = Nothing fmap f (Just a) = Just (f a)
fmap Nothing is called,
Nothing is returned. Otherwise, the passed in function is called on the value contained in the
Just, and a new
Just is returned with the result. This is a much more elegant way to deal with structures, there is no checking required,
fmap knows how to do the right thing.
A Bit Limiting
You may be thinking to yourself “Isn’t this a bit limiting? I can only
fmap using a function that takes 1 argument!” You’d be right, if not for Haskell’s partial function application. Say we want to add 2 to the
Int contained in a
Just. If we didn’t have partial function application, we would have to do something ugly like this:
fmap (\a -> a + 2) (Just 2)
… or maybe something truly awful like this:
addTwo :: Int -> Int addTwo a = a + 2
Luckily for us, we live in a just and righteous world where we can just partially apply
fmap (+2) (Just 2)
While this is a trivial example, I feel it adequately illustrates the point; you can
fmap a function that takes any amount of arguments if you partially apply it!
This leads me to an important point that most literature leaves out: you must apply arguments from left to right, the order of arguments is important! When you’re writing functions, think about how users might want to partially apply them. Take these two functions for example:
padString1 :: String -> Int -> String padString1 s 0 = s padString1 s n = padString1 (s ++ "-") (n - 1) padString2 :: Int -> String -> String padString2 0 s = s padString2 n s = padString2 (n - 1) (s ++ "-")
Both of these functions do the same thing: they take a
String and an
Int, and append n -‘s to the string. The difference is how they can be partially applied:
padString1 has the
String first, so it can only be partially applied with the
padString2 has the
Int first, so it can only be partially applied with the
Int. For this reason, when creating functions, take time to consider these issues. There’s probably a whole blog post to be written about not messing this up. Maybe when I feel qualified, I’ll write it! For now, just keep it in mind.
You’ve heard this word before, right? Map… But where?
instance Functor  where fmap = map
fmap implementation for
List just calls the
map function that you’re probably familiar with. (if you need a refresher, it takes a function, a list, and returns a list with the function called on all members of the original list)
In fact, most structures in Haskell are
Monads in the standard library are
Functors, so it’s a good bet that you can
fmap them. Either way, judicious use of
Functors can make your code much cleaner and more manageable.