Variance in Scala (“Luke, he is your father too”)

When working with Big Data, sometimes it’s useful to remember that powerful products wouldn’t work properly without the tools that build them. It’s possible to start programming in Scala with a few case classes and a bunch of for-comprehensions, but those are only little scratches in a huge ice surface like Scala is. It may not be enough to make your code clean and comprehensible.  I’ve been developing with this programming language for almost 4 years, and every day I discover a new feature that surprises me. That acknowledgement, in the end, is the main reason to keep digging deeper into Scala.

For instance, working with generics can be sometimes really messy, especially when we want to play a little bit with inheritance (which turns out to be very common).

It’s very usual to clash with variance issues in that case, but not as usual as getting a clear idea about what it really implies.

Just to set the tone, we will propose a simple example.

 

Invariance

 

Let’s suppose we’re defining the behavior of some servers’ connection handler…

 

 

…and connection’s definition (we could have two types of connections: user and system connections):

 

 

 

If we want to list all connections given a connection handler, we probably need a logger to print them all and a function for using this logger for each connection:

 

 

Why this freak logger? It’s quite common to invoke ‘println’ and similar methods in Java, and it can be done as well in Scala, but functional purists don’t support this idea.

Let’s focus for a moment on the main ideas of functional programming:

  • a function may have one (or more) input parameters and an only parameter type
  • same input will always generate the same output.
  • instances shall not be modified: state mutation is provided by a new instance of same type.

Having this in mind, what we have provided here is a pretty functional logger that doesn’t perform IO operations that would break the pure nature of functions.

If we continue implementing our method…

 

 

…what we have is a method that performs a foldLeft over the logger, printing on it all connections that belong to given handler.

Easy peasy, but what happens if we try to list the connections of the following connection handler?

 

 

The compiler will complain with the following “wookie-incomprehensible” error:

 

 

If we translate this into human, it means that UserConnection is a subtype of Connection, but we cannot make this condition extensive to connection handler (ConnectionHandler[UserConnection]<:ConnectionHandler[Connection]). How do we fix this?

 

Covariance

In order to make this inheritance relation flexible, we have to slightly modify the signature of ConnectionHandler:

Note the plus sign: it means that any ConnectionHandler[T] will be a subtype of ConnectionHandler[C] if T is a subtype of C.

Now ConnectionHandler is covariant in C and the code that wasn’t working before should work now. We can prove it by the following simple line

 

 

 

which tries to find an implicit evidence of first type is a subtype of the second.

If we tried this with unrelated types like Int and String….

 

 

 

we would get a compilation error like

 

 

Contravariance

On the other hand, we could define a contravariant class C[_] in its parameter type using the minus sign, just in front of parameter type.

This means, given two types T and V, if T is a supertype of V, then C[T] will be subtype of C[V].

 

Confusing? The real question here is, who in his right mind would need this? Maybe the following example is self-explanatory.

Without going too far…Scala’s functions are defined like:

 

 

Not so inspiring so far, but think about the following class hierarchy and the following higher-order function:

 

 

Remember that myFunction signature is syntactic sugar for

 

 

So if you had a Truck, that is a subtype of Car, you couldn’t define your function as follows:

 

 

because it is contravariant in Parameter, Function1 expects a supertype of Car, that’s why you could define your function using Vehicle instead:

 

 

And also don’t forget that covariance in type Result is being satisfied due to Unit (the result type of println) is a subtype of Any. A little bit tricky in the end, but essentially, that was the main idea 🙂


May the functional programming be with you”…

Leave a comment

Please be polite. We appreciate that. Your email address will not be published and required fields are marked