I think you’re right. I’m trying to express a position in an object type hierarchy *and* the possession of a set of traits; the solution to my dissatisfaction is to find a more concise way to express this, and the most concise expression is as an intersection—a construct that Julia simply doesn’t have.

I think it could be backwards-compatible. Simply put, instead of `x::X`

asserting `typeof(x)<:X`

, and instead of having methods dispatch on `typeof`

, imagine if we instead had `x::X`

assert `traitsof(x)<:X`

and methods dispatch on `traitsof`

.

Maybe it could work like this:

##
*Intersecting Types and Traits*

First, introduce a new type. You called it `Meet`

, but to keep the spirit of shaven yaks alive I’ll call it `Intersection`

. In a tree-shaped type hierarchy, the notion of an intersection is useless; `Intersection{Signed, Number}`

is `Signed`

, and `Intersection{String, Int}`

is `Union{}`

. With such a type system, there’s simply no way that `Intersection`

could be useful (hence we don’t have it). But let’s introduce it.

Next, introduce the notion of *multiple hierarchies*. Currently we have only one: the hierarchy of *objects* with a top of `Any`

and a bottom of `Union{}`

, and all types declared in all modules share this hierarchy. A trait such as `Growable`

doesn’t belong on this hierarchy—growability isn’t a category of *thing*; it’s a property that belongs on its own plane of existence.

To express this, maybe we could have a “type module” to declare a completely separate hierarchy:

```
type module Growable
struct Is end
struct IsNot end
end
Growable.Is <: Growable.Any # true
Main.Any != Growable.Any # true
Intersection{Growable.Is, Growable.IsNot} # Growable.Union{}
```

With orthogonality assumed, the intersection of a type in the `Growable`

hierarchy with a type in the object hierarchy will be irreducible—i.e., `Intersection{AbstractString, Growable.Is}`

cannot be reduced to `Union{}`

, but instead must remain `Intersection{AbstractString, Growable.Is}`

. In fact, every trait should exist in its own hierarchy, ideally orthogonal to every other trait, so that intersections with multiple traits can be formed.

Let’s finalize the specification of `Intersection`

. Like `Union`

and `Tuple`

, `Intersection`

types are covariant in their parameters, so that if `T1<:T2`

and `MyTrait.TA<:MyTrait.TB`

, then `Intersection{T1, MyTrait.TA} <: Intersection{T2, MyTrait.TB}`

. Also, like `Union`

, order doesn’t matter.

Next, imagine if we have these definitions:

```
traitsof(x) = Intersection{typeof(x), traits(typeof(x))}
traits(::Type{T}) where T = Any
```

As before, `traits`

could be a special function, in that it dispatches on its argument’s `typeof`

instead of `traitsof`

.

For a type that doesn’t have any traits, you can see how `traitsof(x)`

reduces to `typeof(x)`

, and therefore `x::X`

is backward-compatible. And if `x`

now becomes traitful, but `X`

is still just a supertype, `x isa X`

remains true and `x::X`

still asserts correctly.

Then, to add traits to a type, we can perform a trick similar to the OP:

```
add_traits(T, Tr) = let Trs = Intersection{traits(T), Tr}
eval(:( traits(::Type{var"#T"}) where var"#T"<:$T = $Trs ))
end
```

I think we have all the pieces we need now. With this, function dispatch could work like this:

```
# function call
f(a, b, c, d)
# method signature
f(::Any, ::B, ::CTrait.Tr, ::Intersection{D, DTrait.Tr})
# this method is chosen if:
traitsof(a) <: Any &&
traitsof(b) <: B &&
traitsof(c) <: CTrait.Tr &&
traitsof(d) <: Intersection{D, DTrait.Tr}
# (and, of course, if this is the most specific method
# that satisfies these constraints.)
```

Here’s a question: what should `typeof(Growable.Is)`

be? Should `Growable.Is isa Type`

? If so, then maybe we can make this shorter with `∩(A::Type, B::Type) = Intersection{A, B}`

.

Are there any concerns with this? Would it work? Did I miss something? Can I choose better function names?

##
*Taking a stab at method ambiguities*

One thing I’ve noticed about method ambiguities is that anecdotally it seems like they don’t need to exist. Namely, ambiguities are introduced and resolved, but if methods were written in a different order (i.e., *starting* with the method that solved the ambiguity) the ambiguity would never have existed.

Maybe we can leverage Julia’s dynamic nature—which gets in the way sometimes by forcing method definitions to be sprinkled around type declarations (and vice versa) because of the linear evaluation order—and put it to use here. Namely, every time a method is declared, we could check if it creates any ambiguities with existing methods and throw an error if it does. That would alleviate concerns over method ambiguities by making them strictly impossible to begin with.

Is it generally true that ambiguities can be avoided throughout the course of declaring a function’s methods by properly ordering evaluation? Or am I wrong—would throwing errors on ambiguous method declarations cause some valid method signatures to be unreachable?