About inheritance and abstract types


#1

There’s something about Julia style that I’m just not getting. Let’s say I want a Curve type for a curve in the complex plane. It’s meant to be abstract, since I will have different subtypes of Curve (Line, Circle, etc). But all objects of Curve type should have a pos field that gives the position z as a function of a parameter. This is all that’s needed for a lot of Curve functionality.

In a purer OOP language I’d have Curve as an abstract class that has a pos property and methods that work on all Curves. All those methods know that the object has a pos property, even though the class is abstract.

In Julia, if I have an abstract Curve type and methods that operate on all Curves, those methods will need to use the pos property. Every concrete subtype of Curve then has to be responsible for declaring a pos property, rather than having it be guaranteed as part of Curve. That’s a recipe for mayhem.

Essentially I’m looking for the concept of an interface. What is the Julia equivalent? Or does Julia have another way to look at this?


#2

With the ‘interface’ style, you might be looking for something like

abstract type Curve end

get_pos(c::Curve) = error("No get_pos method defined for curve type $(typeof(c))")

type Line <: Curve
pos::...
end

get_pos(line::Line) = line.pos

do_something(c::Curve) = get_pos(c) + ...

and you’ll get an appropriate run-time error if a given Curve subtype doesn’t conform to the implicit get_pos interface. There is discussion around adding language-level support for a formal interface concept in a future release of Julia.


#3
using Graphics: @mustimplement

abstract type Curve end

@mustimplement get_pos(c::Curve)

...

the same just a little bit more terse using the must implement macro


#4

Its also not just shorter but IMHO it reads really nice and makes the code self documenting. I use this a lot to program against defined interfaces and it works pretty well.


#5

what can I find this @mustimplement macro?


#6

#7

Ah thanks - somehow I misread that this was in Base. I use a similar macro and also find it very useful and clear


#8

It was in base but went to the Graphics package. Actually it does not really belong there but the discussion on interfaces in Base have kind of stopped.


#9

If you want to also “inherit” the behaviour, you can define a fallback method for the abstract type (rather than an error), and override only when required for concrete sub-types.

abstract type Curve end
get_pos(c::Curve) = c.pos

type Line <: Curve
    pos::Float64
end

type Arc <: Curve
    pos::Float64
end
get_pos(arc::Arc) = arc.pos + 1

type Circle <: Curve
    centre::Float64
end

get_pos(Line(2.5))      # use default fallback
get_pos(Arc(2.5))       # use Arc-specific method
get_pos(Circle(2.5))    # ERROR: type Circle has no field pos

#10

One advantage to the Julia approach is dealing with degeneracy. For example, the point (1.0,1.0) is a 0-dimensional curve that could be represented as:

type One <: Curve end
get_pos(one::One) = (1.0, 1.0)

In this example, the savings are trivial, but in general it allows for degenerate subtypes of an abstract supertype to be as light as they can be. If a family of types were to contain a matrix, then it’s reasonable for a degenerate subtype to instead have the identity, I, that doesn’t need to be stored in every concrete instance of the degenerate subtype.