What I think you’re looking for is the sort-of dual to Union
- instead of being a subtype of one of its parameters, you’d want to require T
to be a subtype of ALL of its parameters (I don’t recall the proper type theoretic name, so I’ll just call this Join
for now - think of it as a non-eager typejoin
, just aggregating types without collapsing to Any
or some Union
). That is, in your Platypus
example you’d write it like
abstract type HasDuckBill end
abstract type LaysEggs end
abstract type Mammal end
struct Platypus <: Join{HasDuckBill, LaysEggs, Mammal} end
struct CanadianGoose <: Join{HasDuckBill, LaysEggs, Bird} end
foo(arr::AbstractArray{T}) where {T <: Join{HasDuckBill, LaysEggs}} = "Platypus or Goose"
This can of course lead to lots of ambiguity errors, e.g. if we just add the innocent looking
foo(arr::AbstractArray{T}) where {T <: Mammal} = "Not a bird!"
then
foo(Platypus[])
is ambiguous. This particular approach still has the disadvantage of not being able to easily add new traits to existing objects, since it keeps the type system of julia nominal (see the docs and wikipedia). This is in principle fixable - just don’t require Join
to be declared a supertype, i.e. don’t make it a nominal part of the type system - but I haven’t thought about the consequences of that too deeply. It’s so far the best I’ve come up with to introduce traits & multiple abstract subtyping to julia though I quite like it, but solving the issues with it I haven’t found yet and actually implementing that is probably worth a master level thesis (who knows, maybe I’ll come back to this approach when I go for a masters…)