Wednesday 8 May 2013

An Architect's View: Sean's feedback on my recent article about ColdFusion interfaces

G'day:
It's a good day for this blog when someone like Sean Corfield reacts to something I write with an article-sized response in the comments. I've asked him if it's OK to reproduce this as a "guest-author" article, which he's agreed to. I did this because I freely admit I'm making up my opinion regarding ColdFusion's interface implementation as I go along, whereas Sean knows an awful lot about the subject - and had a hand in their genesis - as far as CFML goes. So if I'm discussing this topic, his opinions are important ones to reflect upon.

Below is a reproduction of his comment posted against my article entitled "Interfaces in CFML. What are they for?". I have some opinions on this - actually most are "yeah, good point" - but I'll cover them in a separate article.

Thanks, Sean, for taking the time to write this:



I was the person who originally requested interfaces in CFML and I lobbied the community to vote for the enhancement. A lot. Eventually it got added - and Tom Jordahl said to me at CFUnited that they only added the feature to shut me up - he didn't think it was a good idea in a dynamic language. And in fact by the time they got around to implementing it, I no longer thought it was a good idea. Hal Helms was also an early proponent and he too changed his mind by the time they were actually implemented.

And then the implementation itself is broken in several ways...

You can't use onMissingMethod() to satisfy an interface contract - even tho' it can happily implement any method and is extremely useful for writing generic objects. You might argue that's an edge case but from a dynamic language's point of view, if I require next() and get() methods and you pass me a CFC that uses onMissingMethod() to implement them, I can't tell purely by using the object. Sure, I can introspect the object and see there's no public methods called "next" and "get" but as long as the object 'works', why should I care that you've used onMissingMethod() instead?

You can't inherit methods to satisfy an interface contract. This is just plain broken. This is one of the fundamental ways that you can adapt classes in other languages that have interface: take a concrete class with the methods you want, extend it and implement the interface (without adding extra code). That just doesn't work in CFML (unless it got fixed in CF10?).

As originally implemented, interfaces in CFML did not support covariant return types. The return type of a method in the implementing CFC had to exactly match the interface declaration. That is just plain broken. A return type that is more specific than the interface declaration should be allowed, because it doesn't break the contract. For example, public any function f(); in the interface can be implemented by public string function f() { return "foo"; } in a concrete CFC (because string is a subset of any so the implementation cannot return anything that violates the contract). All languages that I know of which provide interfaces / abstract classes allow this.

Similarly, contravariant argument types should be allowed (because CFML doesn't have method overloading). Any function that accepts a more general argument type implements the contract of the original, more specific, argument type because when called thru the interface, you can't actually pass a more general value.

I think I've raised all these as bugs with Adobe over the years. I didn't keep track of the bug numbers and I don't know if any of them got fixed. But these are some of the reasons that I think CFML's interface feature is unusably broken.

In addition, interfaces just don't buy you much in a dynamic language - and it's why you don't see them in other dynamic OO languages, not in Ruby, not in Python (although 2.6 introduced a syntax for abstract base classes - an ugly syntax as far as many people are concerned, it seems), not in Smalltalk - the granddaddy of OO languages.

C++ does not have interfaces, but it does have abstract classes and pure virtual methods - and it supports multiple inheritance. Java does not support multiple inheritance but introduced interfaces instead. There's some controversy as to whether it really introduced interfaces because it chose not to support multiple inheritance but certainly most (possibly all?) multiple inheritance situations (in C++) can be rewritten to Java using interfaces.

In a statically typed language, interfaces or abstract base classes make sense: they define a contract for the _type system_ - that's their sole purpose. If you don't have a type system, or at least , only a runtime type system, a compile-time construct makes no sense. And of course the compromise of interfaces in CFML is that they are checked at runtime, when an instance is created, and they fail to leverage several of the dynamic features of the language (and also fail to implement some of the static type systems' features that they are drawn from!).

CFML allows methods to be dynamically added to objects at runtime. This can be very useful for frameworks to "decorate" objects without needing to force inheritance on users. In a dynamic language, if a function requires that an object it is passed can have next() and get() called on it, it's perfectly acceptable to add those at runtime and the function will be none the wiser. An interface prevents that option.

BTW, I was also the person that asked for - and helped specify - onMissingMethod() since Ruby and Smalltalk (and others) have that feature. It's a powerful feature in a dynamic OO language. That was a good choice. Interface was a poor choice.


Again: thanks Sean. Good stuff!

--
Adam