Monday, December 21, 2015

Understanding Contramap

Recently I've been trying to get my head around what contravarient functions and contramap are used for in practice. In particular I've been searching for some practical examples that turn the types of the function into something concrete. This all started because after implementing a lot Json parsing / writing using Play's Json libraries. In doing so I'd periodically have to use contramap to implement a Write and I wanted to understand what it actually did.

This led me to here: http://blog.tmorris.net/posts/functors-and-things-using-scala/index.html. and here: http://igstan.ro/posts/2013-10-31-contravariant-functors-an-intuition.html.

And now my own attempt to explain.

The problem:

The thing that confused me straight up was this:


Being relatively new to functional programming my mind jumps straight to lists when I think about functors. How can you have a List[Int], a function that converts a String to Int and then somehow generate a List[String]? The function is backwards, this can't be possible? The problem is lists are applicable for the covariant map function, but not contramap.

An example:

Below I take what I've learnt from the above blogs and apply to a very basic serialisation. For me this finally made the idea of contramap stick. But before we talk about types and functions I wanted to try and talk about the concrete example.

Serialsation is pretty common concept. If we look at Json, the idea of taking a string of characters in a generic structure and converting it into an object in memory well understood. When we talk about reading Json we are implementing an abstraction that is covariant and which implements map. For example, if you a have a container (let's call it a Reads[T]) that can read a string into a JsObject (so this would be Reads[JsObject]), and you have a function that can convert a JsObject into the type you are interested (let's call it Company), then it should be possible to create Reads[Company]. The function would look something like:

The implementation of this would probably use the underlying Reads[JsObject] to convert our serlizaed format to a JsObject and then our function to convert the JsObject to Company. So this matches the definition of a covariant map:

But what about contramap?

For this example, let's forget about JsObject and Json and take a really simple example of serializing an object to a string. Let's define a very basic serialisation function.

Along with this we create a simple class and it's serialisation implementation.
Very basic. Just print out a key and a value. Now let's implement contramap. This will allow us to serialise any object using only the traits defined above.


Which matches the contrmap functor defined by Tony. In this case A is Writeable and B is Test and F is writes. If we switch these out we have:

And finally we can define ourselves a Writes[Test] using contramap.