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.:(==)andBase.hashtogether so your type works as aDictkey or in aSet. - Define
Base.islesssosort,minimum, andmaximumwork.
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.