Note: information on this page refers to Ceylon 1.0, not to the
current release.

Inheritance, refinement, and interfaces

This is the fourth leg of the Tour of Ceylon. In the
previous part we looked at attributes,
variables, setters, and control structures. In this section we're going to
learn about inheritance and refinement (known as "overriding" in many
other languages).

Inheritance is one of two ways Ceylon lets us abstract over types. (The
other is generics, which we'll get to later in this tour.)
Ceylon features a flavor of multiple inheritance called
"mixin" inheritance. You might have
heard or experienced that multiple inheritance is scary and complicated
and, indeed, that's kinda true of multiple inheritance in C++. But mixin
inheritance in Ceylon comes with certain restrictions that strike a
good balance between power and harmlessness.

Inheritance and refinement

In object-oriented programming, we often replace conditionals (if, and
especially switch) with subtyping. Indeed, according to some folks, this
is what makes a program object-oriented. Let's try refactoring the Polar
class from the previous leg of the tour into two classes,
with two different implementations of description. Here's the superclass:

Notice that Ceylon forces us to declare attributes or methods that can
be refined (overridden) by annotating them default.

Subclasses specify their superclass using the extends keyword
(here's why),
followed by the name of the superclass, followed by a list of arguments
to be sent to the superclass initializer parameters. It looks just like
an expression that instantiates the superclass:

Ceylon also forces us to declare that an attribute or method refines
(overrides) an attribute or method of a superclass by annotating it actual
(not "overrides" like Java).
All this annotating stuff costs a few extra keystrokes, but it helps the
compiler detect errors. We can't inadvertently refine a member or the
superclass, or inadvertently fail to refine it.

Notice that Ceylon goes out of its way to repudiate the idea of "duck"
typing or structural typing. If it walks() like a Duck, then it should
be a subtype of Duck and must explicitly refine the definition of walk()
in Duck. We don't believe that the name of a method or attribute alone is
sufficient to identify its semantics. And, more importantly, structural
typing doesn't work properly with tools.

Shortcut syntax for refinement

There's a more compact way to refine a default member of a superclass:
simply specify its refined implementation using =>, like this:

You can refine any function or non-variable value using this streamlined
syntax.

Note that this shortcut syntax does not allow annotations. If you need to
add documentation or other annotations to the refining member, you must
use the more verbose syntax.

Refining a member of Object

Our Polar class is an implicit subtype of the class
Object
in the package ceylon.language. If you take a look at this class, you'll
see that it has a default attribute named
string.
It's common to refine this attribute to provide a developer-friendly
representation of the object.

Polar is also a subtype of the interface
Identifiable
which defines default implementations of
equals()
and hash.
We should definitely refine those:

As you've probably guessed, if (is ... ) works just like if (exists ... ),
testing and narrowing the type of a value. In this case it tests the type of
that and narrows to Polar if that is indeed an instance of Polar.
We'll come back to this construct
later in the tour.

Using the shortcut syntax for refinement that we just met, we could
abbreviate the above code like this:

(But in this case, the shortcut syntax is perhaps not an improvement.)

Abstract classes

Now let's consider a much more interesting problem: abstracting over the
polar and cartesian coordinate systems. Since a cartesian coordinate isn't
just a special kind of polar coordinate, this is a case for introduction of
an abstract superclass:

Ceylon requires us to annotate abstract classes abstract, just like Java.
This annotation specifies that a class cannot be instantiated, and can define
abstract members. Like Java, Ceylon also requires us to annotate "abstract"
members that don't specify an implementation. However, in this case, the
required annotation is formal. The reason for having two different
annotations, as we'll see
later,
is that nested classes may be either abstract or formal, and abstract
nested classes are a bit different to formal member classes. A formal
member class may be instantiated; an abstract class may not be.

Note that an attribute that is never initialized is always a formal
attribute. Ceylon doesn't initialize attributes to zero or null unless
you explicitly tell it to!

One way to define an implementation for an inherited abstract attribute is
to use the shortcut refinement syntax we saw above.

Notice that Ceylon, like Java, allows covariant refinement of member types.
We were able to refine the return type of rotate() and dilate(), narrowing
to Polar from the more general type declared by Point. But Ceylon doesn't
currently support contravariant refinement of parameter types. You can't
refine a method and widen a parameter type. (Someday we would love to fix this.)

Of course, you can't refine a member and widen the return type, or change
to some arbitrary different type, since in that case the subclass would no
longer be a subtype of the supertype. If you're going to refine the return
type, you have to refine to a subtype.

Cartesian also covariantly refines rotate() and dilate(), but to a
different return type:

We usually don't try to prevent other code from extending a class (though
there is a final annotation like in Java). Since only members explicitly
declared as supporting refinement using either formal or default can be
refined, a subtype can never break the implementation of a supertype. Unless
the supertype was explicitly designed to be extended, a subtype can add
members, but never change the behavior of inherited members.

Abstract classes are useful. But since interfaces in Ceylon are more
powerful than interfaces in Java, it often makes more sense to use an
interface instead of an abstract class.

Interfaces and "mixin" inheritance

From time to time we come across a case where a class needs to inherit
functionality from more than one supertype. Java's inheritance model doesn't
support this, since an interface can never define a member with a concrete
implementation. Interfaces in Ceylon are a little more flexible:

An interface may define concrete methods, attribute getters, and attribute
setters, but

The satisfies keyword
(not implements like Java)
is used to specify that an interface extends another interface or that a class
implements an interface. Unlike an extends declaration, a satisfies
declaration does not specify arguments, since interfaces do not have parameters
or initialization logic. Furthermore, the satisfies declaration can specify
more than one interface.

Ceylon's approach to interfaces eliminates a common pattern in Java
where a separate abstract class defines a default implementation of some
of the members of an interface. In Ceylon, the default implementations can
be specified by the interface itself. Even better, it's possible to add a
new member to an interface without breaking existing implementations of the
interface.

Ambiguities in mixin inheritance

It's illegal for a type to inherit two members with the same name, unless the
two members both (directly or indirectly) refine a common member of a common
supertype, and the inheriting type itself also refines the member to eliminate
any ambiguity. The following results in a compilation error:

To fix this code, name must be declared default in both User
and Party and explicitly refined in Customer. We can delegate to
one of the super-interface implementations using the syntax
(super of Party).name.

The of operator performs a statically safe typecast. That is,
a cast that is guaranteed to succeed at runtime. We'll meet other
uses for it later, but here you can think of it as widening the type
of the expression super from User&Party to Party, thus resolving
the ambiguity as to which inherited definition of name should be
called.