In my prior post on Vala’s language features, I discussed enums and how I appreciated Vala’s implementation of them. I feel that Vala’s enums straddle an interesting line of utility and pragmatism. It took me a while to learn about their features, partially because documentation has been sparse (but is getting better) and partially because as a C / C++ / Java programmer, I’d had hammered into me a set of expectations about enums that Vala didn’t quite adhere to. (I had a similar learning curve, for many of the same reasons, about Vala’s lock keyword.)
Learning interface in Vala was a similar experience. Consider the Java Language Specification’s opening statement about interfaces, which Vala’s interfaces look to be a descendant of:
An interface declaration introduces a new reference type whose members are classes, interfaces, constants and abstract methods. This type has no implementation, but otherwise unrelated classes can implement it by providing implementations for its abstract methods.
Compare to this statement in the C# Programmer’s Guide:
Interfaces consist of methods, properties, events, indexers, or any combination of those four member types. An interface cannot contain constants, fields, operators, instance constructors, destructors, or types. It cannot contain static members. Interfaces members are automatically public, and they cannot include any access modifiers. … The interface itself provides no functionality that a class or struct can inherit in the way that base class functionality can be inherited.
What’s interesting about Java and C# interfaces is what they can’t provide: implementation. Interfaces are often touted as a sane solution to the problems surrounding multiple inheritance, but it always struck me as odd (and a bit of a blind-spot on the part of their boosters) that interfaces provide no reusable code. After all, isn’t that the name of the game? Especially for a technique that’s replacing a form of inheritance, which is all about reusable code?
(I have a pet theory that if one was to study the history of the development of software development technologies — languages, tools, paradigms, all of it — it’s primarily a history of reusable code. How much money and manpower has been dumped into this holiest of Holy Grails: write once, debug a bit more, reuse a billion times.)
Like enum and lock, Vala offers an interesting interpretation of interface with a couple of surprises. I’m not sure how much of it is due to the mechanics of GTypeInterface, Vala’s influences from Eiffel, or simply a reflection of Jürg’s vision, but it’s cool all the same.
Let’s start with a simple Vala interface that looks familiar to any Java or C# programmer:
interface Agreeable { public abstract bool concur(); }
In-the-know Java programmers will say that the public and abstract keywords are allowed but not required; in C#, they’re simply not allowed. But in Vala, neither are optional:
interface Agreeable { bool concur(); }
produces this compiler output:
error: Non-abstract, non-extern methods must have bodies
That seems to suggest that non-abstract, non-extern methods in interfaces can have bodies (“must implies can”, kind of a programmer’s variant of Kant’s argument from morality).
And sure enough, this will compile:
interface Agreeable { public bool concur() { return true; } }
What’s going on here? Simple: reusable code.
Vala interfaces are much more than hollow declarations of required methods. Interfaces are full-fledged members of the object hierarchy, able to provide code to inheriting classes. The reason an interface is not an abstract class is that an interface has no storage of its own, including a vtable. Only when an interface is bound to a class (abstract or concrete) is storage allocated and a vtable is declared. In other words, while the above is legal, this is not:
interface Agreeable { bool yep = true; public bool concur() { return yep; } }
gets you this:
error: Interfaces may not have instance fields
Bingo. That boolean is a member instance, which requires instance storage, which an interface does not have by itself.
So what good is allowing an interface to provide reusable code if it has no fields of its own? There’s a number of patterns of use, in particular Facade-style methods. For example, an interface could declare a handful of abstract primitive methods and offer a helper method that uses them in concert according to contract:
interface Agreeable { public abstract bool concur(int i); public abstract string explanation(bool concurred); public void process(int i) { if (concur(i)) stdout.printf("Accepted: %d %sn", i, explanation(true)); else stdout.printf("Not accepted: %d %sn", i, explanation(false)); } } class OnlyOdds : Agreeable { public bool concur(int i) { return (i & 0x01) == 0x01; } public string explanation(bool concurred) { return concurred ? "is odd" : "is not odd"; } } class OnlyMultiplesOfTen : Agreeable { public bool concur(int i) { return (i % 10) == 0; } public string explanation(bool concurred) { return concurred ? "is a multiple of ten" : "is not a multiple of ten"; } }
First, note that the implementations of concur() and explanation() in the classes don’t use the override keyword even though you must use the abstract keyword in the interface. I’m not sure of the reasoning, but so it goes.
Also know that virtual methods, signals, and virtual signals have their own peculiarities with interfaces. I’ll deal with them in another post.
So, a pretty contrived and very silly example, but notice how process() understands Agreeable’s interface and contract and hides those details behind a single method call. This is useful.
Going back to those language specifications earlier, remember that Java and C#’s interfaces cannot contain static methods. In Vala they can:
interface Agreeable { /* ... */ public static void process_many(Agreeable[] list, int i) { foreach (Agreeable a in list) a.process(i); } }
This allows for binding aggregator-style code with the interface itself, rather than building utility namespaces or classes. Again, this is useful.
However, if the following code is written:
Agreeable[] list = new Agreeable[2]; list[0] = new OnlyOdds(); list[1] = new OnlyMultiplesOfTen(); Agreeable.process_many(list, 10);
you get this compiler error:
error: missing class prerequisite for interface `Agreeable', add GLib.Object to interface declaration if unsure
What’s this about?
It’s due to another freedom in Vala that is lacking in Java and C#. Vala classes don’t have to descend from a single master class (i.e. Object). Unlike the other two languages, if a Vala class is declared without a parent, there is no implicit parent; Vala registers the class with GType as a fundamental type. If you don’t know what that means, read this. You probably still won’t know what that means, however.
Because Agreeable is declared without a prerequisite class, Vala can’t produce the appropriate code to store it in a tangible data structure, in this case, an array. (Update: As Luca Bruno explains in the comments, this is because of Vala’s memory management features.) This solves the problem:
interface Agreeable : Object {
What this means is that any class that implements Agreeable must descend from Object (i.e. GObject), meaning we need to change two other lines in the code:
class OnlyOdds : Object, Agreeable { class OnlyMultiplesOfTen : Object, Agreeable {
Although Agreeable now looks to descend from Object, it does not. It merely requires an implementing class to descend from Object. (A subtle difference.) Interfaces can also require other interfaces, and like classes, it can require any number of them:
interface Agreeable : Object, Insultable, Laughable {
Like requiring Object, this means that any class implementing Agreeable must also implement the other interfaces listed (Insultable, Laughable). This does not mean that Agreeable must implement those interfaces’ abstract methods. In fact, it can’t, one place where code reuse can’t occur.
Prerequisites also mean that Agreeable’s code can rely on those interfaces in its own code, and therefore can do things like this:
interface Agreeable : Object, Insultable, Laughable { /* ... */ public void punish(int i) { if (concur(i)) laugh_at(); else insult(); } }
… where laugh_at() is an abstract member of Laughable, insult() is an abstract member of Insultable, and of course concur() is its own abstract member. In other words, because Agreeable knows it’s also Insultable and Laughable, it can treat itself as one of them.
It’s easy to go crazy with interfaces, prerequisites, and helper methods, but most great languages have their danger zones of excess and abuse — features that are the hammer that makes everything look like a nail. Still, I think code reuse is the most important goal of any programming technology — language, tool, or paradigm — and I’m glad Vala has given it some thought in terms of interface.
Great read! Thanks
Interesting, though one thing was a little unclear. Does Vala support multiple inheritance in the C++ sense – i.e classes deriving from multiple concrete parent classes? Or does it permit only a single concrete parent, but allowing multiple “interfaces”?
In any case, it sounds to me that for Vala to use the term “interface” for this is a bit of a misnomer – the concept sounds more like what most people would call “mix ins”, a means to inject common code into a class through means other than inheritance.
Vala has single inheritance, just like Java. And yes, allowing interfaces to contain methods is described in the Vala tutorial as a mixin.
Vala’s purpose is exposing (the beauty of) the GObject type system. It inherits the term “interface” from GObject.
Nice! It’s pretty similar to the Scala concept of traits, which are also great for code reuse.
Thinking that code reuse is the point of inheritance is the root cause of mountains of unmaintainable C++ class hierarchies. If code reuse is your goal, composition is preferable. In C++, anyway. I don’t know anything about Vala, but my position might be why interfaces work the way they do in Java and C#.
I’m not sure I agree that inheritance leads to “mountains of unmaintable C++” code (multiple inheritance, perhaps). More to the point, if inheritance wasn’t designed for code reuse, what was it designed for? It’s one thing to say it does code reuse poorly, another to say that it wasn’t designed with that in mind.
I think the issue isn’t with inheritance – it’s with the misuse of it. Inheritance is good for code re-use when the child classes are more specific cases of a common concept, and it makes sense for those child classes to inherit common behaviour.
However, using inheritance where that *isn’t* true is a recipe for a maintenance nightmare. Over the years, C++ developers have had the bad habit of using inheritance to access stuff that really doesn’t belong in the hierarchy, but which is convenient to do that way. And yes, multiple inheritance tends to encourage it, because you’re not forced to think about the design so much as when constrained to a single parent class.
Nice post, thanks. The reason behind “error: missing class prerequisite for interface `Agreeable’, add GLib.Object to interface declaration if unsure” is that Vala does memory management, therefore it requires _ref() and _unref() methods for those objects, which GObject provides (as well as fundamental classes created by Vala).
Very nice overview of how interfaces in other languages differ from Vala and GObject. I might point out that
(i % 10) == 0
determines whetheri
is a multiple of 10, not a power, though.Ah, good catch. I’ve fixed the examples.
I still think the sole reason Vala exists is that C people are too afraid of C++. Seems like you reinvented another wheel…
I think the sole reason you are posting here is because you are using C++ and still waiting for your code to compile. At least there’s an awful lot of C++ people writing comments in blog posts all the time…
If there’s one language begging for reinvention, it’s C++.
They should be.
After investigating alternatives to C++ for a couple of years, Vala is the only one that really hits the nail for me.
Thanks for the informative blogs guys
C++ is absolute abomination. Even some of it’s main contributors and standards committee members (e.g. Alexandrescu) acknowledge it as being a useful but deeply and unfixably flawed language. The only things keeping C++ around are legacy code and (until recently) lack of a decent replacement. The only people singing it’s praises now are those with the sunk cost of having learned it but too much laziness to learn something new.
tl;dr they’re not “reinventing” the wheel — they’re replacing a horribly designed square wheel with a round one.