Functors: Values with context

Functors are a really important concept to understand if you plan on doing any programming in any strongly typed functional programming language. The thing is they appear to be such a simple thing at first glance that most novices tend to dismiss them as a fairly insignificant thing. Afterall, how useful can an interface be that only has one method?

Other then just having to implement map (or fmap, language dependent), a proper functor should also obey the two functor laws. These laws are the functor identity and the functor composition laws as defined bellow:

fmap id = id  
fmap (g . h) = (fmap g) . (fmap h)  

The combination of these laws ensure that your implementation of (f)map only changes the value without altering it's context. So there it is, i dropped the magic "C" word, context. Yet, this is exactly what a functor is, a value with context.

A functor is a value itself, that contains an inner value with an associated context. An example of this is the Maybe monad in haskell. An instance of Maybe is a value that wraps an inner value that may or may not exist. Whether or not the inner value exists and how (f)map behaves when invoked on a Maybe instance is informed by the context.

This description of context has the feeling of a statefull thing and this is a correct feeling. Most typically the state of a context for a functor will be specified through it's type. In the case of the Maybe functor, it's two possible contexts are represented in the types Just a and Nothing to represent existence and non-existence respectively. It's defined as follows:

data  Maybe a  =  Nothing | Just a  

The function you supply to (f)map to operate on the inner value is said to operate on the inner wrapped value within the contextual bounds of the functor. In the case of Maybe, (f)map for Just a applies the supplied operation on the inner wrapped value and returns the result of the operation in a Just b, while (f)map for Nothing will unconditionally do nothing and return Nothing. Their typeclass definition is as follows:

instance Functor Maybe where  
    fmap _ Nothing  = Nothing
    fmap f (Just a) = Just (f a)

Heres a simple example on using Maybe to safely perform an age based calculation on an age we may or may not have.

age   = Just 24  
adult = map (\x -> x >= 18) age  

Effectively getting at the value through side channels can be looked at as side stepping contextual bounds and can be a fairly dangerous thing to do.

An example of this danger can be found in Scala's Option functor. Option is basically just Scala's version of Haskell's Maybe. The Dangerous part of the Option functor is the #get() method. Calling #get() on an Option returns the inner wrapped value if it exists, if not an exception is thrown. The throwing of this exception is what i'm referring to as the dangerous behaviour.

The result of calling #get() on a None.

var nothing = None();  
var value   = nothing.get(); // <-- Unconditionally throws exceptions  

The result of calling #get() on an Option.

def double(optional: Option[Int]) = {  
    optional.get() * 2; // <-- May or may not throw an exception
}

We can get safe access to the inner wrapped value by operating on it within the contextual bounds of the functor and eliminate all the boiler plate existence checking and exception handling code we'd need otherwise for simply depending on the context to just do the right thing.

The safe way to return the double of an Option[Int] with (f)map

def double(optional: Option[Int]) = {  
    optional map (_*2) // <-- Never throws and only operates on `Some`
}

In short, functors are like safe fluffy pillows for values, while non contextual side channel access is like playing russian roulette with yourself.