DEV Community

Jesse Phillips
Jesse Phillips

Posted on • Originally published at he-the-great.livejournal.com

Covariance and Multiple Interface Inheritance

Originally posted to my blog in 2014

(The names used for interfaces, classes, functions may not depict good design; they have been chosen only to make conveying the capability more clear)

interface AAAAA { aaaaa aaA(); }
interface aaaaa {}

class AaAAaA : AAAAA {
    aaaaa aaA() {
        return aaaaa.init;
    }
}

class aAA : AaAAaA, aaaaa {
}
Enter fullscreen mode Exit fullscreen mode

Excuse me. I think my computer just sneezed.

Interesting thing happened today, I was fighting with C# about type safety. And it seems the Lang.Next talk was released which had some discussion about contra/covariance and why have it.

I prefer static typing. I want the compiler to yell when using something wrong and I lean on that ability heavily. People love REPL from dynamic languages because they get to try things and see how they work, types allow for the that same principle. Making some modifications to a type in terms of structure and behaviors expected the compiler will explain when someone isn't upholding their end of the bargain. And this gives the opportunity to either re-evaluate the design choice being made or to update types to match the changes.

For that reason I want structure my code in a way that the compiler will validate a contract is being fulfilled. This means casts are not an option, such an operation tells the compiler to ignore what it knows and assume the object will fulfill the rule of the type desired. Covariance for function return types was the needed feature to achieve these goals, C# was missing it. I looked into parameterizing my types, but that didn't quit fit into the whole; I've settled on explicit interface implementation , which just adds boiler plate and I don't have mixins to solve that problem.

D, however, does provide such a feature let's dive into it.

class A {
    A foo() { return A.init; }
}

class B : A {
    override B foo() { return B.init; }
}
Enter fullscreen mode Exit fullscreen mode

The idea here is to provide covariance on return type, and is not valid in C#. Notice that class B has overridden foo() of class A, but it does not adhere to the contract that foo() returns A. Instead it returns B, as we know that class B is_a A it is type safe to make this change. When calling a.foo() where the type of 'a' is known to be class A and the object is of class B, the returned type will be A.

Hopefully the example I've contrived will depict why this is so great. Please don't consider this a design pattern, understand why it works and if coming across a reason this applies then feel free to be frustrated that the language you're coding in doesn't support it.

interface LoanOffice {
    LoanItem getItemOfLoan();
}
interface LoanItem {}
Enter fullscreen mode Exit fullscreen mode

A LoanOffice is going to provide a method that obtains the item that has a loan on it. I'm thinking financial loan, so LoanItem may just be paperwork about the loan. LoanOffices are really dumb as they only ever give out a single loan, they may do some other useful things like restock staples but lets concentrate on that single loan.

interface CarDealer : LoanOffice {
    Car getItemOfLoan();
}

interface CamperDealer : LoanOffice {
    Camper getItemOfLoan();
}
interface Car : LoanItem {}
interface Camper : LoanItem {}
Enter fullscreen mode Exit fullscreen mode

Here are some specific types of LoanOffices, each one will use covariance to provide a very specific LoanItem. Basically the same as the A and B class example but using interfaces.

class RVDealer : CarDealer, CamperDealer {
    interface Truck : Car {}
    class RV : Truck, Camper {}
    override RV getItemOfLoan() {
        return RV.init;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the first implementation of a CarDealer and a CamperDealer. Someone selling an RV is going to have a LoanOffice which provides a Car LoanItem and it provides a Camper LoanItem since the RV is both of these. I also threw in the Truck interface which for the sake of argument is a Car. The Truck could go further down the path of covariance had there actually been requirements for being a Car.

void handleCarLoan(Car c) {}
void handleHomeLoan(Camper d) {}
Enter fullscreen mode Exit fullscreen mode

And here is a simplification of why I desired covariance. In this case I'm showing functions which take a specific LoanItem (my actually case was effectively working with the dealers themselves). An RVDealer should be able to provide a LoanItem to these two functions and there should be no reason to cast. An RVDealer already knows that the type it provides fulfills both contracts even if Car and Camper are distinct types.

void main() {
    auto rvDealer = new RVDealer();
    handleCarLoan(rvDealer.getItemOfLoan());
    handleHomeLoan(rvDealer.getItemOfLoan());
Enter fullscreen mode Exit fullscreen mode

D allows calling of these two functions because it has verified that the types will fulfill all needed contracts. C# would require a cast eliminating the compile-time checks desired. Note: had I instead stored my RVDealer into a CarDealer or LoanOffice variable the compiler would not allow both of these calls to succeed. This is because the type information says that getItemOfLoan() will return a Car and LoanItem respectively but not the Camper.

    void handleCarLoan(RVDealer.Truck c) {}
    handleCarLoan(rvDealer.getItemOfLoan());
}
Enter fullscreen mode Exit fullscreen mode

And in case you were wondering, the handleCarLoan() can be specialized to our Truck interface and our RVDealer will give us the type to satisfy that requirement.

Conclusion

The ability to use covariance for function return types helps greatly in eliminating type casts and boiler plate. I wouldn't state it has a frequent need but it provides options and logically follows the contracts of the interface.

Top comments (0)