Is it possible to create a supertype to the trait somehow?
A trait already is like a supertype. So in the example I showed above, we have the trait IsMyArray{T}
and we can add members to it by just adding methods to the ismyarray
function.
I still find your example quite unclear, and from what you say I suspect you can get away with something much simpler, but I’m going to assume the actual functionality you need is something like an abstract type. In that case, I’ll try again to give an example that might answer your question using traits.
First, lets define our types and aliases:
struct Bulk end
struct MyType
data::Float64
end
const MultiLayer = Vector{MyType}
Now, lets define a trait called Structure
, and make a function has_structure_trait
that can decide if a given type is a member of our trait. For now, we’ll only register Bulk
and MultiLayer
as members, but we can add more whenever we like.
struct Structure{T} end
struct Not{T} end
has_structure_trait(::T) where {T} = Not{Structure{T}}()
has_structure_trait(::MultiLayer) = Structure{MultiLayer}()
has_structure_trait(::Bulk) = Structure{Bulk}()
Now we can define our functions. The main difference from regular dispatch rules is that we’ll need functions that send their input to has_structure_trait
to decide if they’re kosher to dispatch on:
absorption(structure) = absorption(has_structure_trait(structure), structure)
function absorption(::Structure{T}, structure) where {T}
r= reflection(structure)
t= transmission(structure)
return 1-r-t
end
reflection(s) = reflection(has_structure_trait(s), s)
reflection(::Structure{Bulk}, s::Bulk) = 0.1
reflection(::Structure{MultiLayer}, s::MultiLayer) = sum(x -> (x.data), s)
transmission(s) = transmission(has_structure_trait(s), s)
transmission(::Structure{Bulk}, s::Bulk) = 0.2
transmission(::Structure{MultiLayer}, s::MultiLayer) = sum(x -> 2*(x.data), s)
Now lets test it at the REPL.
julia> absorption(Bulk())
0.7
julia> absorption([MyType(0.1), MyType(0.2)])
0.09999999999999987
julia> absorption([1, 2])
ERROR: MethodError: no method matching absorption(::Not{HasStructureTrait{Array{Int64,1}}}, ::Array{Int64,1})
Closest candidates are:
absorption(::HasStructureTrait{T}, ::T) where T at REPL[10]:2
absorption(::Any) at REPL[9]:1
Stacktrace:
[1] absorption(::Array{Int64,1}) at ./REPL[9]:1
[2] top-level scope at none:0
If we want to improve that error message, we can do this:
julia> absorption(::Not{Structure{T}}, x::T) where {T} = throw("$T does not have the Structure Trait.")
absorption (generic function with 4 methods)
julia> absorption([1,2])
ERROR: "Array{Int64,1} does not have the Structure Trait."
Stacktrace:
[1] absorption(::Not{HasStructureTrait{Array{Int64,1}}}, ::Array{Int64,1}) at ./REPL[19]:1
[2] absorption(::Array{Int64,1}) at ./REPL[9]:1
[3] top-level scope at none:0
So what we’ve done is made a trait called Structure{T}
and we can add members to it using the has_structure_trait
function. That can be done at any time and is very extensible. It is analogous to defining an abstract type, except it has fewer restrictions, and we can even do it to types we don’t own.
The downside is that we have to introduce methods like
absorption(structure) = absorption(has_structure_trait(structure), structure)
which decide if a certain structure gets passed on to
absorption(::Structure{T}, structure::T) where {T}
and this can add a fair amount of verbosity as you can see.
Basically the way to think about traits is that we are hacking the dispatch system so that we can just write a function has_structure_trait
which decides what method gets used on a certain piece of data instead of the normal rules from the type system.
Edits:
I’ve included some clarity improvements and the suggestion from @Tamas_Papp to dispatch on values instead of types.