Structural typing, or did OO get interfaces wrong?

! Warning: this post hasn't been updated in over three years and so may contain out of date information.

Lapsang logoLately I have been doing a lot of thinking about programming languages and how ideas change over time on what constitutes good language functionality. As I enjoy playing around with grammars and parsers, I naturally started thinking about the idea of designing a hypothetical language of my own and what form it would take. One area that I was keen to address is OO’s interfaces and inheritance. It’s a topic I’ve had many heated debates with various people over the past couple of years and so it seemed a natural place for me to start. As I was trying to work out how best to express my ideas, Microsoft launched TypeScript. It implements what I feel interfaces should have been all along and even gave me an “official” name for the technique: structural types.

To begin with, I should explain why I think there is a problem with interfaces as they are implemented by most OO languages. To do this, I’ll use two real-world examples.

Example 1 – Vectors and arrays in ActionScript.
Imagine you have a method in ActionScript that can take an array or vector (a typed array) and can do something with that data set. Now what I’d really like to do is something like:

But I can’t do that, as there is no “IArray“, ie Array and Vector do not implement a shared interface. So the code needs to be something like:

Adobe chose not to provide a common interface, so we must write nasty code to work around that.

Example 2 – ForEach method for C#’s List<> class
In C#, there is a nice ForEach() method that List<> implements, that can be used to write the above method as:

However, once more there is a problem. Good software practice says I should program to interfaces, yet I’ve used List<>, not IList<> as the parameter’s type. I have to do this, as IList<> doesn’t implement ForEach(). The reasons for this are well documented, but such explanations beg the question “why does List<> have a ForEach() method?” I could create an extension method for IList<> to work around it, but by adding thta, I’m then increasing the size of the interface. As Martin Fowler explains, this is generally bad practice. What I really want is for all collection classes in C# to implement an IForEach interface, which simply defines a requirement for a ForEach method and I’ll decide whether side effects are a good or bad thing with such methods.

I think the “I’ll decide” point is key here. The problem is that we have frameworks that constitute a set of design decisions made by others and we are trapped by those decisions. What I really want in my hypothetical language is a way to override those decisions by applying interfaces after the event. Which is where structural typing comes in.

The idea of structural typing is that types are deemed to implement an interface if the class implements all the requirements of that interface. So with the C# example, I might have a structural typed interface thus:

(I have re-used the struct keyword to denote that its a structural type.) Now at both compile-time and runtime, a class could be checked against the interface’s contract and, if it met it, it would treated as implementing that interface. So my code could be written as:

SomeMethod would then work with List<> and would work with any type I wrote that implemented ForEach(). Further it could work with any existing type to which I added an appropriate extension method. This is a far better way of using interfaces in my view and really does encourage small interfaces as they can be written at any stage to only describe the features a functional unit needs. So a double win.

As a final note, TypeScript’s creator is non other than Anders Hejlsberg, the very person who also created C#. Perhaps, just perhaps, he will be inspired to retrofit the idea of structural typing to C# one day…

5 thoughts on “Structural typing, or did OO get interfaces wrong?

  1. For the C# example the IList interface is arguably correct, but List should also implement something like IWalkable or some such, which is where ForEach would exist.

    I suspect the main practical downside of a system such as this is the potential slowdown/memory-consumption-for-caches when you add runtime created objects into the mix.

    Oh, and what happens when objects coincidentally fulfil a structural type? That could be the source of interestingly obscure bugs 🙂 I guess structural typing requires discarding a little type safety, because the type no longer guarantees behaviour, just what something looks like in terms of method names.

  2. @mnem,

    I think you are right David, with regard to speed. This does seem the obvious disadvantage with structural typing, in that there has to be a runtime overhead. Whether it is significant though remains to be seen.

    I don’t follow what you are saying about types “coincidentally fulfilling a structural type. Surely this is no different to using inheritance to derive a type that no longer guarantees the behaviour of the base type. For example if I extend Human with a new type, Building, that throws a not implemented exception when the Walk() method is called?

  3. Completely artificial example: imagine you have an audio program. In it there are 2 objects with a record() function. On one it returns a Boolean indicating recording has started from the default audio source, on the other it returns the name of an album. At one point your interface tells you the name of your album is “True” and every sound you make is being recorded without your knowledge. Somewhat contrived, but I’m sure there are subtler variations.

    The difference with inheritance is that you are making some statement about what, uh, class an object is. Genus? Whatever the right word is. Structural typing seems like it would allow you to accidentally mix objects from entirely different provenance by accident. Perhaps in practice this would rarely happen. I’m a programmer, so I have an urge to poke things to see where they break 🙂

    I agree that potential runtime overhead is no reason to dismiss the system – look at what people used to say about JavaScript 🙂

  4. An artificial example perhaps, but a good one. As long as the return type is taken into account, then they would be treated as different in this case, but extending your example, “record” can mean “start recording” or “record set”. So two classes, one that records something (and returns true if recording started OK) and another that returns true if a new record has been set would appear to have the same signature. Food for thought there.

    I too was thinking of JavaScript when considering performance hits. People both tolerate performance issues if the benefits outweigh them, and clever people find ways of working magic with potential performance bottlenecks.

  5. You might find this interesting:

    Also, this hits on why JS doesn’t have more than a handful of types and doesn’t allow users to extend that set. Structural typing certainly would allow you to work around that (and is essentially what languages like JS/ their users do), but I do think that mnem has a pretty good point about accidentally satisfying contracts.

    Then again, static typing on its own only gives you so much in terms of enforcing contracts: knowing that something is a number doesn’t mean you don’t have to ensure that it is in the correct range for your use-case, etc.

    That’s where contracts, guards, etc. come in. And that’s the general framework in which, I think, the whole idea of structural typing should be implemented.

    In JS, this will, at some point, hopefully come in the form of guards:

Comments are closed.