This lightweight package provides convenience macros for traits (similar to and inspired by SimpleTraits.jl). However, function behavior depending on traits can now be specified via intuitive if
/else
syntax using @iftraits
.
This package is in an early stage of development; whether it's concept really is useful is not yet clear to me. Feel free to suggest features, improvements and (of course) bugfixes!
Consider the example
@traitdef IsNice
@traitimpl IsNice(Int)
@iftraits f(x) = IsNice(x) ? "Very nice!" : "Not so nice!"
which (unsurprisingly) leads to
julia> f(5)
"Very nice!"
julia> f(5.0)
"Not so nice!"
Internally, we just replace IsNice(x)
by a function call trait{IsNice}(x)
and return true
in the case of a fulfilled trait and false
in the case of a missing trait.
Because the compiler is smart, the return value of these functions gets detected at compile time and the trait dispatch does not have to be done at runtime.
From this example the advantage over other packages is not apparent. The real power instead lies in the simplicity of defining functions which depend on multiple types. As far as I know, usually the approach to this problem is either to use combined traits (which in my opinion is not very intuitive) or to define a bunch of different functions depending on several input traits (which imo is kinda messy).
In our case, this can be solved with generic syntax:
# dummy example
abstract type Musician end
struct OperaSinger<:Musician end
struct ChartsSinger<:Musician end
struct BadSinger<:Musician end
struct Composer<:Musician end
@traitdef CanSing, CanCompose
@traitimpl CanSing(OperaSinger, ChartsSinger)
@traitimpl CanCompose(Composer, ChartsSinger)
In this setting we ask whether it makes sense to have a
compose something for b
.
@iftraits function compose_for(a, b)
if CanCompose(a)
if CanSing(b)
println("works")
else
println("compose for someone else")
end
elseif CanCompose(b)
if CanSing(b)
println("compose for yourself and save the money")
else
println("maybe you should change your job?")
end
else
if CanSing(b)
println("play Puccini then!")
else
println("wtf is this bullshit")
end
end
end
We get the intended behavior:
julia> compose_for(OperaSinger(), OperaSinger())
play Puccini then!
More examples can be found here.
My current understanding is that special traits of the form BelongTogether(X, Y)
are just a (more or less) natural extension. Let's say we have a function ship
which we would like to formulate using traits.
struct Romeo end; struct Juliet end
belong_together(a, b) = false
belong_together(a::Romeo, b::Juliet) = true
belong_together(a::Juliet, b::Romeo) = true
ship(a, b) = belong_together(a, b) ? "go marry" : "nothing holds forever"
Now we can use the following trick:
# Multi is just a dummy trait, we could instead use an arbitrary type
@traitdef Multi, BelongTogether
@traitimpl BelongTogether(Multi)
# assume types X and X
function ⊕(a, b) end
⊕(::Romeo, ::Juliet) = Multi(); ⊕(::Juliet, ::Romeo) = Multi()
Thus, we can now write
ship(a, b) = @iftraits BelongTogether(⊕(a, b)) ? "go marry" : "nothing holds forever"
Note however that we need a different function ⊕
for every multi-type trait. This pattern could of course be simplified by some new feature (something along defining struct ⊕{T} end
and extending @traitdef
/@traitimpl
macros).
Credit goes of course to Tim Holy (see here the THTT) and Mauro Werder. If you feel like the credit for your contribution is missing, don't hesitate to contact me.
If this package does not fit your use case, you might want to have a look at these packages:
- SimpleTraits.jl (and the deprecated Traits.jl)
- WhereTraits.jl
- CanonicalTraits.jl
- BinaryTraits.jl
Here are some other helpful resources:
- Official documentation regarding traits, it's imho kinda difficult to understand
- Tom Kwong's book Hands-on design patterns and best practices with Julia: proven solutions to common problems in software design for Julia 1.x
- Christopher Rackauckas' blog post Type-Dispatch Design: Post Object-Oriented Programming for Julia
- Frames Catherine White' blog post The Emergent Features of JuliaLang: Part II - Traits
- GitHub repositories Object Orientation and Polymorphism in Julia and Dispatching Design Patterns from Aaron Christianson