Recently, I started programming in Rust, and like many others, I noticed how powerful the design language and solutions around traits can be. This prompted me to investigate Julia’s interface and trait-related packages:
and questioning whether Julia even needs the nominal type system While “nominal type system” might not be the best term for Julia’s type hierarchy, I’m referring here to its explicit single-inheritance abstract type design.
Building a Rust-like but more flexible Type System in Julia
It turns out that it’s possible to construct a Rust-like type system in Julia, though there are notable limitations. For instance, DuckDispatch.jl doesn’t support multiple trait inheritance, which feels unnatural for a trait system. This limitation is understandable, as Julia itself lacks multiple inheritance, making it challenging to express such concepts.
On the other hand, WhereTraits.jl allows dispatch based on arbitrary conditions, but you need to manually define the trait hierarchy. So far, WhereTraits.jl seems to be the closest candidate for implementing a practical, Rust-like trait-based system. Here’s an example of how to construct a trait-based (Rust-like) type hierarchy using WhereTraits.jl
:
Example
using WhereTraits
ATrait(::Type{T}) where T = isdefined(Main, :func1) && isdefined(Main, :func2) && hasmethod(func1, Tuple{T,Int64}) && hasmethod(func2, Tuple{T,Int64,Int64})
BTrait(::Type{T}) where T = isdefined(Main, :func3) && hasmethod(func3, Tuple{T,Int64,Int64,Int64}) && ATrait(T)
CTrait(::Type{TA}, ::Type{TB}) where {TA, TB} = ATrait(TA) && BTrait(TB) && hasmethod(func_ab, Tuple{TA, TB})
@traits_order (Main).process(x::T) where T begin
BTrait(T)
ATrait(T)
end
@traits function process(x::T) where {T, !ATrait(T)}
42
end
@traits function p(x::T) where {T, ATrait(T)}
100
end
@traits function process(x::T) where {T, ATrait(T)}
func1(x, 1) + func2(x, 2, 3) + p(x)
end
@traits function process(x::T) where {T, BTrait(T)}
func1(x, 1) + func2(x, 2, 3) + func3(x, 4, 5, 6) + p(x)
end
@traits func_ab(x::TA, y::TB) where {TA, TB, CTrait(TA, TB)} = 1
@traits function process(x::TA, y::TB) where {TA, TB, CTrait(TA, TB)}
func_ab(x, y) + process(x) + process(y)
end
struct ConcreteB end
func1(x::ConcreteB, y::Int64) = y
func2(x::ConcreteB, y::Int64, z::Int64) = 3 + y + z
func3(x::ConcreteB, y::Int64, z::Int64, w::Int64) = 6 - y - z - w
struct ConcreteA end
func1(x::ConcreteA, y::Int) = 1 + y
func2(x::ConcreteA, y::Int, z::Int) = 3 + y + z
struct Concrete end
# Test the dispatch
x1 = Concrete()
x2 = ConcreteA()
x3 = ConcreteB()
println(process(x1))
println(process(x2))
println(process(x3))
println(process(x2, x3))
println(process(x3, x2))
Challenges with Julia’s Current Type System
Julia’s reliance on single-inheritance hierarchies often forces users to cram unrelated concepts into a single hierarchy, which can be infeasible and lead to convoluted designs. For example, consider this notorious “dispatch monster” for matrix multiplication:
Dispatch monster
*(X::Union{ReshapedArray{TX,2,A,MI} where
MI<:Tuple{Vararg{SignedMultInverse{Int64},N} where N} where
A<:DenseArray, DenseArray{TX,2}, SubArray{TX,2,A,I,L} where
L} where I<:Tuple{Vararg{Union{AbstCartesianIndex, Int64,
Range{Int64}},N} where N} where
A<:Union{ReshapedArray{T,N,A,MI} where
MI<:Tuple{Vararg{SignedMultInverse{Int64},N} where N} where
A<:DenseArray where N where T, DenseArray},
A::SparseMatrixCSC{TvA,TiA}) where {TX, TvA, TiA}=
This complexity highlights the ergonomic challenges that arise from Julia’s lack of support for orthogonal trait hierarchies and multiple inheritance. The absence of multiple inheritance forces its users to put concrete type into a single hierarchy even when it is not feasible. While I understand that adding multiple inheritance would require a significant overhaul of Julia’s type inference engine and related systems, I still feel that its omission was a missed opportunity. What’s the big deal in having yet another source of ambiguity if MethodError: method is ambiguous.
already exists.
Questions
- What are the chances that Julia 2.0 will move away from its brittle single-inheritance hierarchy and adopt a more robust, trait-based type system? This already seems feasible in Julia 1.x, aside from ad hoc handling of multiple inheritance.
- Where can I find discussions related to the design and goals of Julia 2.0?
- Is Julia 2.0 even being discussed seriously at this point?
- What is the best way to contribute to implementing features like a trait-based type system in Julia? How can I effectively begin learning the details of language implementation? Simply poking around random parts of the GitHub repository feels somewhat inefficient.