Saturday 11 May 2013

Interface inheritance what one can and what one cannot do

G'day:
Today's article is brought to you via my baby netbook, as I sit on my bed in my hotel room in Portumna, Co. Galway in the the Republic of Ireland. My ex-wife and my son (25-months-old) live in Portumna, and this is why I pop across to Ireland every two or three weeks (you might have heard me mention it on Twitter or elsewhere). I get to see my boy for four hours on Sat and Sun, every - as I said - 2-3wks. This sux, but that's the way it is.

I only have internet here if I go sit halfway down the stairs, and despite that being doable as I'm the only guest here this weekend, it's a bit cold for that today. So I'm writing this article without the benefit of docs or fact checking! Wish me luck. Or enjoy picking apart whatever it is I end up getting wrong. Heh: in reality I will check everything before I publish... the internet works in the bar downstairs, so I'll proof-read and publish over an early evening Guinness.


Right, well all the nonsense from the last day or so seems to have cleared itself up now (don't ask. Or if you must, check the articles and comments against said articles over the last few days), so I'll get on with talking about interfaces. They've really piqued my interest now.

There's been three earlier articles in this series to catchup with if you're just landing here for the first time:

OK, so on with some new stuff.

Multiple Inheritance

You might recall in an earlier article I had this code:

// IListable.cfc
interface {
    boolean function next();
    string function asString();
}

This was a bit of a work around so that I didn't have to "pollute" my earlier IIterable interface with an asString() method, as that would make no sense:

// IIterable.cfc
interface {

    boolean function next();

}

An Iterable interface should only busy itself with iterating sort of behaviour, not serialising sort of behaviour. The "IListable" interface was one way of working around this. Another approach would be to use interface inheritance. I could have these three interfaces:

// Loopable.cfc
interface {
       boolean function next();
       any function get();
}


// Serialisable.cfc
interface {
       string function toString();
}

// Listable.cfc
interface extends="Loopable,Serialisable" {

}

Note that unlike with components, with interfaces one can have multiple inheritance. So now our Listable collection simply implements this one interface:

// ArrayCollection.cfc
component implements="Listable" {

And, likewise when a Listable object is needed as an argument:

public UnorderedList function init(Listable collection) {

Note that if UnorderedList wasn't so rigid in its requirement to have a Listable, then ArrayCollection.cfc could still implement both interfaces itself:

// ArrayCollection.cfc
component implements="Loopable,Serialisable" {

This would mean that ArrayCollection could be used whenever a Loopable was called for, or a Serialisable. But not a Listable. Basically the thing to take away from this is that a component can implement multiple interfaces, but an interface can extend multiple interfaces. Just a reminder: a component cannot extend multiple components.

Polymorphism

One thing to note here, is that interface inheritance is polymorphic like component inheritance is. If a function expects an argument of a type that implements IParent, it can be passed an argument of type IChild, provided IChild extends IParent. EG:

// IParent.cfc
interface  {
       public void function parentFunction();
}

// IChild.cfc
interface extends="IParent" {
       public void function childFunction();
}

//Child.cfc
component implements= "IChild" {

       public void function parentFunction() {
              writeOutput ("G'day from Child.parentFunction()");
        }

       public void function childFunction() {
              writeOutput("G'day from Child.childFunction()");
        }

}

// test.cfm
c = new Child();
tester(c);

public void function tester(IParent p){
      p.parentFunction();
}

This works fine, outputing "G'day from Child.parentFunction()". One difference between CFML and Java here is that in Java, one cannot call p.childFunction() within tester(), because childFunction() is not an IParent method. CFML doesn't apply this constraint.

Possibly predictably by now, this notion is also supported in the context of component inheritance too. If a method requires an argument of type I, and a component Parent implements that, and another component Child doesn't declaratively implement I, but does extend Parent, then that is fine:

// I.cfc
interface {
       public Parent function factory();
}

// Parent.cfc
component implements="I" {
       public Parent function factory(){
               return new Parent();
        }
}

// Child.cfc
component extends="Parent" {
       public Child function factory(){
               return new Child();
        }
}

// test. cfm
c = new Child();

function test(I object){
       return object.factory();
}

result = test(c);    
writeDump(variables);

Note here that the method definition of factory() in I.cfc requires it to return a Parent; whereas the factory() method in Child returns a Child. However because a Child is a type of Parent, this is fine: anywhere a Parent could be used, a Child can likewise be used, after all.

However what if Parent didn't implement factory()? This should be OK, as we're not using a Parent object in this code, we're using a Child object and that does implement factory() correctly, and a Child object does fulfill the contract that I.cfc requires. But unfortunately this does not work in CFML. If I adjust Parent.cfc to be like this:

// Parent.cfc
component implements= "I" {

}

Then and re-run that code, I get an error:

CFC Parent does not implement the interface I.
The factory method is not implemented by the component or it is declared as private.


Whilst - strictly speaking - this is true, it should not matter. Consider this analogous Java example:

// I.java
public interface I {
        public Parent factory();
        public String greet();
}

// Parent.java
public abstract class Parent implements I {
}

// Child.java
public class Child extends Parent {

        public Parent factory() {
               return new Child();
       }

        public String greet(){
               return "G'day from " + this.getClass().getName();
       }

}

// test.java
public class Test {

        public static void main(String[] args) {
              
              Child c = new Child ();

              Parent result = test(c);
              
              System. out .println(result.greet());

       }
        public static Parent test(I object){
               return object.factory();
       }

}

Note that the Parent class is defined as abstract, which is obviously a construct CFML doesn't have. However one doesn't actually need that. Parent.java will give a warning if you try to compile it as non-abstract, but it will actually compile and run. So clearly the intent is that the concept in general should work, so it should also work in CFML. I've raised a bug for this: 3558908.

CFML typefulness

Also note that whilst all this type-inheritance works fine for one's own component types, it doesn't work for inbuilt CFML types. I'm not surprised by this on the whole, but as CFML is supposed to be loosely typed, I think when there's situations in which the type "any" is specified in an interface, then any sort of return type / argument type should be allowed. For example:

// I.cfc
interface {
       any function returnsAny();
       void function takesAny(required any x);
}

// ReturnsString.cfc
component implements= "I" {

       string function returnsAny(){
               return "G'day world" ;
        }

       void function takesAny (required any x){
       
        }

}

// PassesString.cfc
component implements= "I" {

       any function returnsAny(){
               return "G'day world" ;
        }

       void function takesAny(required string x){
       
        }

}

// PassesString.cfc
component implements= "I" {

       any function returnsAny(){
               return "G'day world" ;
        }

       void function takesAny(required string x){
       
        }

}

If I run test.cfm, I get this output:

Return type mismatch. The returnsAny function does not specify the same return type in the ReturnsString ColdFusion component and the I ColdFusion interface.

Data type mismatch. The takesAny function does not specify the same data type for the x argument in the PassesString ColdFusion component and the I ColdFusion interface.

So CF is very literal here when it comes to what "any" is. As I indicated, I get why it's like this, but I don't think it should be.

I've raised a ticket for this: 3558899.

Mix-ins


Speaking of typelessness, Sean put me onto an interesting one here. Consider this code:

// Testable.cfc
interface {
       void function f();
}

// Bare.cfc
component {

}

// test.cfm

bare = new Bare(); // at this point, this is an empty object

void function f(){
       // this is the function that Testable.cfc requires
}

bare.f = f;    // now bare fulfills the contract requirements of Testable.cfc

// this function requires an argument which is a Testable
void function test (required Testable o){
}

test(bare);    // bare should fit the bill now, but this line will error

There's an argument to be had that given CFML is loosely type, and that it as well allows mix-ins, then this code should work. By the time bare is used, it is actually a completely valid Testable object. However CF will error when Bare.cfc is first compiled (up on the first line):

The O argument passed to the test function is not of type Testable.

This is probably incorrect behaviour for a dynamic language like CFML.

And I've raised a ticket for this too 3558904.

onMissingMethod()?

Another example Sean mentioned in his earlier article is that he thinks one should be able to fulfill an interface contract with a an object that has an onMissingMethod() handler. I'm personally not so sure about this. I can see where he's coming from, but this doesn't sit quite so well with me than that previous mix-in example. I hasten to add my hesitation is not for any good reason, it's just that I can understand a case when the object actually does have the very methods that the interface needs when that object comes to be type-checked, but it's a slightly different thing to just permit any object that has an onMissingMethod() handler through validation on the off chance it might be able to fulfill the interface contract. I guess my position is that I'm good with the interface contract not being checked until it's needed, but the type check should still be done. Otherwise one might just as well not bother with the type on the argument at all.

Extending an existing class to implement an interface

Something that Sean described as a bug seems to work for me (on CF9 & CF10, anyhow). He described this:
You can't inherit methods to satisfy an interface contract[...]: take a concrete class with the methods you want, extend it and implement the interface (without adding extra code).
(the omission - [...] - is just for brevity, it does not change the context of what he said).

However this is what I think he means:

// I.cfc
interface {
       void function f();
}

//Parent.cfc
compenent {
       public void function f(){
       }
}

// Child.cfc
component extends= "Parent" implements= "I" {
}

<!--- test.cfm --->
<cfset o = new Child()>

Note that whilst Child.cfc doesn't implement the f() method that I.cfc requires, it does inherit it from Parent.cfc. Sean indicated this doesn't work. It did for me. Maybe it was bust in CF8 or in Railo or something? Dunno.

Guinness

That's about all that comes to mind regarding CFML interfaces at the moment. It's time for me to pop downstairs and get a Guinness, get online, and publish this article.

Sláinte!

--
Adam