Skip to content
AgileLogics
6 min read

Interfaces: Making Your Types Feel Built-in

Prerequisites: Parametric types

Julia has no interface keyword. Instead, an interface is a set of functions you implement for your type. Implement them, and every function written against the interface works with your type.

Iteration

To make a type work with for loops, implement Base.iterate:

struct Countdown
    from::Int
end

Base.iterate(c::Countdown, state = c.from) = state < 1 ? nothing : (state, state - 1)
Base.length(c::Countdown) = c.from
Base.eltype(::Type{Countdown}) = Int

for n in Countdown(3)
    print(n, " ")
end
println()
println(collect(Countdown(5)))
println(sum(Countdown(4)), " ", maximum(Countdown(4)))
println([n^2 for n in Countdown(3)])
3 2 1
[5, 4, 3, 2, 1]
10 4
[9, 4, 1]

iterate(x) returns the first (item, state) pair, iterate(x, state) the next, and nothing when finished. With just that (plus length, and eltype so collect knows to build a Vector{Int} instead of a Vector{Any}), collect, sum, maximum, comprehensions, and much more work.

Indexing

struct Squares
    n::Int
end

Base.getindex(s::Squares, i::Int) = 1 <= i <= s.n ? i^2 : throw(BoundsError(s, i))
Base.length(s::Squares) = s.n
Base.firstindex(s::Squares) = 1
Base.lastindex(s::Squares) = s.n

s = Squares(10)
println(s[3], " ", s[end])
9 100

getindex powers s[i], and lastindex makes end work.

Abstract supertypes as interfaces

An abstract type can stand for "things that support these functions". Write generic code against the abstract type and define the required functions for each subtype:

abstract type Shape end
struct Circle <: Shape
    r::Float64
end
struct Square <: Shape
    side::Float64
end

area(c::Circle) = π * c.r^2
area(s::Square) = s.side^2
name(s::Shape) = lowercase(string(nameof(typeof(s))))

describe(s::Shape) = "a $(name(s)) with area $(round(area(s), digits = 2))"

shapes = [Circle(1), Square(2), Circle(0.5)]
foreach(s -> println(describe(s)), shapes)
println("total: ", round(sum(area, shapes), digits = 3))
a circle with area 3.14
a square with area 4.0
a circle with area 0.79
total: 7.927

describe and sum(area, shapes) work for any future Shape too, as long as it defines area.

Comparison and hashing

  • Define Base.:(==) and Base.hash together so your type works as a Dict key or in a Set.
  • Define Base.isless so sort, minimum, and maximum work.
struct Version
    major::Int
    minor::Int
end
Base.isless(a::Version, b::Version) = (a.major, a.minor) < (b.major, b.minor)
Base.show(io::IO, v::Version) = print(io, "v", v.major, ".", v.minor)

vs = [Version(1, 10), Version(1, 2), Version(0, 9)]
println(sort(vs))
println(maximum(vs))
Version[v0.9, v1.2, v1.10]
v1.10

The manual's "Interfaces" chapter lists the functions for iteration, indexing, AbstractArray, and broadcasting.