How to subtype a type alias?

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.

5 Likes