Interface Polymorphism

I am currently employed as a Java developer, but I’ve only held this position for a short while. Before that, my official job title was “data engineer” which made me want to throw up because:

  1. Big data isn’t real
  2. I have a chemical engineering degree and nothing I do in my day job is anything remotely close to approaching the rigor of proper engineering

But that’s besides the point.

The point is that, even though I am a professional java developer, I don’t know that much about it. I care much more about the niceties and warts of my favorite hobby languages than about the one I use nearly every day for my employer.

That being said, I came across a facet of java today which I had completely forgotten about. One which I am sure there is a name for. The thing in question was a function return type of an interface.

Now, being a Haskellfag this immediately struck me as odd and I won’t lie that it got on my nerves. I know that it’s ignorant to say that typeclasses are basically interfaces but this is the space that they occupied in my mind. I had totally forgotten how interfaces could work in practice.

Accepted Intuition

One way I think of typeclasses and interfaces is that, in isolation, they hide information about their implementing type and expose only a subset of the total operations that may be available to use on that particular implementing type.

class Iterable t where
  next :: t a -> Maybe (a, t a)

Here is a typeclass which generalizes the type signature laid forth by Data.List.uncons :: [a] -> Maybe (a, [a]). By itself, an Iterable constraint tells you nothing other than the fact that you can pop an element off of a non-empty structure.

For example:

tail' :: Iterable t => t a -> Maybe (t a)
tail' = fmap snd . next

The above function can take the tail of any iterable structure, not just a list. With the caveat that the function itself doesn’t know anything about its input argument other than that it has an implementation for next.

So now consider a function with this signature:

generalizeIterable :: Iterable t => [a] -> t a

It should be immediately obvious (to anyone familiar with Haskell) that, in this context, this function has no possible implementation.

The reason for this is because the type variable t must polymorphically represent any type which has an Iterable instance. t cannot be bound to a concrete type, but you can’t return anything but a value of a concrete type from a function. Even if this function were to return a [a] or a Maybe a, this is a type error of this flavor:

• Couldn't match type ‘t’ with ‘[]’
  Expected: t a
    Actual: [a]
  ‘t’ is a rigid type variable bound by
    the type signature for:
      generalizeIterable :: forall (t :: * -> *) a.
                            Iterable t =>
                            [a] -> t a

That this is a type error trains our intuition about how Haskell programs generally work, type-wise. They flow from general types to specific types, or polymorphic functions to concrete types.

Interfaces Are Not Type Classes

Consider now the rough Java analogue:

public interface Iterable<T> {
  public Optional<Pair<T, Iterable<T>>> next(Iterable<T>);
}

public class IterableList<T> implements Iterable<T> {...}

public Iterable<T> generalizeIterable(IterableList<T> xs) {
  return xs
}

Excuse any mistakes in this code, I really don’t care to actually try to compile this snippet to see if its valid.

But notice how generalizeIterable is perfectly acceptable!

What Is Going On?

Consider that the Java implementation has a Haskell analogue via existential types.

data IterableProxy a where
  IterableProxy :: Iterable t => t a -> IterableProxy a

generalizeIterable :: [a] -> IterableProxy a
generalizeIterable = IterableProxy

So we can hide the Iterable constraint inside the constructor of an IterableProxy and then turn a concrete type, [], into a less concrete type IterableProxy a. This type is less concrete because we lose access to the information that the Iterable instance is a [] and therefore we lose access to all of the functions from Data.List, keeping just next that’s declared in Iterable.

Why Would You Want This?

You wouldn’t. I was going to say something like

Java generally has expansive enough interfaces to be useful in cases where
an interface constraint is the only information you have. Haskell type classes are generally
more terse and are therefore strictly less useful in more cases than concrete types are.

but after one half-second of thought I realize this is nonsense. I’ll double down on my opinion that this pattern of returning an interface rather than a concrete type in Java is pukeworthy and I sincerely implore Java developers to repent from their ways and learn Haskell.

Further Reflections

I think, on a semantic level, hiding type information in the return type of a function just obfuscates the meaning of code and even further confuses implementation. Why, as the author of such code, do you return an interface and not a concrete implementation of an interface? Your type signature is telling me that you yourself do not know what your function is producing! Why would you not declare a new class, with a meaningful name, implement your interface, and enrich the informational landscape of your program rather than obfuscate it?

To me, this a design decision that should live in the internals of a library but in this case it leaked out and caused me immeasurable emotional anguish.

Thank you for reading.