I was recently working with a struct passed around during view construction that contained a special msg function with this signature:
(Maybe msg, UpdateType) -> msg
This function lets the user encode a regular msg and an UpdateType msg into a single msg value. But that’s beside the point. What’s important here is that the function is invariant on the msg
type parameter.
Functions structured like this are covariant on msg: anything -> msg
Functions structured like this are contravariant on msg: msg -> anything
Functions structured like this are invariant on msg: msg -> msg
(The rules get a bit more complicated when anything
is itself a function that contains the msg type in its signature, but we can ignore that.)
You’re probably familiar with regular map functions that look like this: (a -> b) -> Container a -> Container b
. Turns out this is only possible with containers that are covariant on their type parameter. Most things are covariant though (lists, maybes, results, etc.), which is why most things have a map function.
This container is contravariant over a:
type ContravariantThing a = ContravariantThing (a -> Int)
It’s not actually possible to define a regular map function for this type. (Try it!) Instead, if you want to change the type parameter, you need to use this function:
contramap : (b -> a) -> ContravariantThing a -> ContravariantThing b
contramap bToA (ContravariantThing innerFunc) =
ContravariantThing (\bValue -> innerFunc (bToA bValue))
It almost looks like the mapping is happening in reverse. In a way, it is! But that’s a story for another time.
If you have an invariant thing, you need both a forward mapping function and a backward mapping function in order to change the type parameter:
invmap : (a -> b) -> (b -> a) -> InvariantThing a -> InvariantThing b
Which leads me right back to the struct I mentioned earlier. We wanted to implement a map
function for the element type we use to build our UI. (Like Html.map
.) Since the Element type uses this invariant-on-msg struct it isn’t actually possible! I struggled with the compiler for half an hour before I made the connection that (Maybe msg, UpdateType) -> msg
is invariant. With that knowledge I realized that it simply isn’t possible to implement an ordinary map function for the struct. That understanding probably saved another several hours of struggling with the compiler.
Guess this means that the category theory/functional programming stuff can occasionally be useful.