Combining SimpleTraits.jl traits on one type

I recently got a little into SimpleTraits - and I like it quite well. But I have a question considering the combination of traits.

Consider the following MWE (just noticed, maybe AbstractA is not necessary in general)

using SimpleTraits
abstract type AbstractA end
abstract type DecoA <: AbstractA end
# A few concrete types
struct A <: AbstractA end
struct A1 <: DecoA end
struct A2 <: DecoA end
struct A3 <: DecoA end

# just some function
f(::AbstractA, x) = x+2
g(::DecoA, x, y) = x+y

Now I want to do two decorators, for example

@traitdef IsNice{T}
@traitimpl IsNice{T} <- is_nice(T)
is_nice(::Type{<:A1}) = true
is_nice(::Type{<:A2}) = false
is_nice(::Type{<:DecoA}) = false

@traitdef IsCool{T}
@traitimpl IsCool{T} <- is_cool(T)
is_cool(::Type{<:A1}) = false
is_cool(T::Type{<:A2}) = true
is_cool(T::Type{<:DecoA}) = false

@traitfn f(a::TA, x) where {TA <: DecoA; IsNice{TA}} = g(a,x,3) #(a)
@traitfn f(a::TA, x) where {TA <: DecoA; IsCool{TA}} = g(a,x,5) #(b)
  1. what is a good way to write the case !IsNice and !IsCool ?
  2. what is a good way to write the case !IsNice or !IsCool ?

More generically, if I have a set of traits is there a way to define the fallback, that none of them is active?

I want it to fallback to just using f i.e. I want A3 act similar to A.

print("normal:$(f(A(),0))") # prints: normal:2
println("cool $(f(A2(),0))") #prints: cool: 5
println("neither nice nor cool $(f(A3(),0))") # does not work, since there is no !IsCool cases should behave like A (see Q1)
println("nice $(f(A1(),0))") # does not work since it first „checks“ Cool (defined last; see Q2) 

This brings up the idea of preceedence - i.e.

  1. If I have A4 the is nice and cool, which one is called first, (a) or (b) and how could I steer that?
1 Like

From the README on SimpleTraits.jl it appears dispatching on multiple traits is not yet possible.

However WhereTraits.jl could possibly support it!

using WhereTraits

abstract type AbstractA end
abstract type DecoA <: AbstractA end

# A few concrete types
struct A <: AbstractA end
struct A1 <: DecoA end
struct A2 <: DecoA end
struct A3 <: DecoA end

# just some function
f(::AbstractA, x) = x+2
g(::DecoA, x, y) = x+y

is_nice(::Type{<:A1}) = true
is_nice(::Type{<:DecoA}) = false

is_cool(::Type{<:A2}) = true
is_cool(::Type{<:DecoA}) = false

@traits f(a::TA, x) where {TA <: DecoA, is_nice(TA)} = g(a,x,3)
@traits f(a::TA, x) where {TA <: DecoA, is_cool(TA)} = g(a,x,5)
@traits f(a::TA, x) where {TA <: DecoA, !is_cool(TA), !is_nice(TA)} = "without this the multiple dispatch is ill-defined for `A3`"

@show f(A(),0)
@show f(A1(),0)
@show f(A2(),0)
@show f(A3(),0)
2 Likes

Thanks for this nice solution,
actually @mateuszbaran proposed a solution without WhereTraits, since that comes with a lot of dependencies and we want to use it in a lightweight interface. His solution is here

Just out of curiosity: Your approach would (in the current form) still error on both set to true (my A4) so I would have to give precedence for either cool or nice in a fourth function definition, right?

1 Like

You could just set a function with proper type signature

@traits f(a::TA, x) where {TA <: DecoA, is_cool(TA), is_nice(TA)} = "dispatch for `A4`"

It’s a shame WhereTraits.jl is such a heavy weight, I would also think twice before using it as a major dependency in a package. The solution by @mateuszbaran seems like a nice way around the problem!

2 Likes

Oh, I actually exactly had such a line in mind with my question. One disadvantage of that approach (which Mateusz avoids) is that for 3 one would need 8 cases, for 4 traits 16 and so on, it does not scale well; but thanks for your input, I will report, when we managed to have a solution.

Just to finish this thread, we just released our solution to the question asked, in principle using a list of traits (ordered) here Decorating/Extending a Manifold · ManifoldsBase.jl

2 Likes