Announcing Traits.jl - a revival of Julia traits

Hi @visr. Thanks for your question

Here some advantages of Traits.@traits over SimpleTraits.jl

  1. (this is the most important advantage from my point of view)
    no need for @traitimpl or in general a separation between a standard julia function and a Trait

  2. the @traits functions are kind of compatible with existing standard julia functions in that they only overwrite the respective standard dispatch part. I.e.

    @traits f(a::Vector{Int}) = true
    # or more complex
    @traits f(a::Vector{Int}) where {isSometrait(a), isAnothertrait(a)} = true
    

    really only overwrite the previous definition of f(::Vector{Int}), as usual in Julia.

  3. Bool functions are just one concrete case of what @traits supports (it seamlessly also works with multi-value traits like defined in the standard Julia Iterator interface Base.IteratorSize)

  4. it works with multiple traits at once, in a transparent manner. Concretely if you dispatch on say both isTrait1 and isTrait2 like

    @traits f(a) where {isTrait1(a)} = "first"
    @traits f(a) where {isTrait2(a)} = "second"
    

    and have an a for which both isTrait1 and isTrait2 are true, you will be prompted to fill the ambiguity as usual, which can be solved by defining

    @traits f(a) where {isTrait1(a), isTrait2(a)} = "both"
    
  5. also traits with two or more arguments are naturally supported. E.g. if areinorder(a1, a2, a3, ...) checks whether a1 <= a2 <= a3 ..., this just works

    @traits f(a, b, c) where {areinorder(a, b, c)} = true
    
  6. the implementation of @traits is very easy to explain on high-level. It just splits up the @traits function into an outer and an inner function, the outer doing the standard julia dispatch, and the inner doing all the extra dispatch functionalities.

  7. a small syntax-nice-to-have: You can freely mix normal where syntax with the new syntax. No need for extra character ; to separate them, but the implementation is smart enough to distinguish the different functionalities on its own.

  8. another syntax-nice-to-have: You do not need to have explicit TypeVariables like

    @traits f(a::T) where {T, eltype(T)::String} = "something"
    

    but if your function is similar to eltype in that if it gets a value, it returns eltype(typeof(value)) you can simply do

    @traits f(a) where {eltype(a)::String} = "something
    

There probably a couple of disadvantages, however I haven’t really used SimpleTraits.jl and hence cannot tell too much. One obvious point is that so far no Traitor.jl like syntax is supported.


Concerning your second question, how this approach compares to Traitor.jl, I hope that some points from above already clarify differences.

  • One crucial aspect is again, that Traitor.jl relies on a special convention about Traits types.
    Concretely, in Traitor.jl you have to define a proper type-hierarchy for your traits-types where the abstract type constructor is overloaded respectively - if I understood it correctly. With Traits.@traits nothing like that is needed, it just works with whatever function you have, e.g. Base.IteratorSize.
  • Another thing is the syntax: I prefer using Julia where, while Traitor.jl syntax was developed before there even was a Julia where, which back then was the best syntax available, but today feels a bit overloaded and unintuitive for me.
  • last but not least, the Traits.@traits syntax is already now pleasantly stable and I guess with only waiting until JuliaCon it is soon going to be production-level stable

I hope that can clarify some of your questions. And open some others :wink:

13 Likes