Inserting new abstract type into existing hierarchy?

Suppose an existing package Barnyard defines

abstract type Animal end
abstract type Dog <: Animal end

Let’s say that in my package Zoo I define two new types:

using Barnyard
abstract type Zebra <: Animal end
abstract type Lizard <: Animal end

But, now I want certain method to apply to Zebra and Dog while a different one should apply to types such as Lizard. I want an intervening type Mammal that sits above both Zebra and Dog and below Animal. (Also maybe a Reptile type for Lizard to go under, but that’s easy.)

Let’s suppose the maintainers of Barnyard reject my PR for some reason, and I don’t want to maintain a hard fork. Is there a way to inject an intermediate level into the type hierarchy from outside the package? It seems like it is not possible:

using Barnyard
abstract type Mammal <: Animal end
abstract type Zebra <: Mammal end
abstract type Dog <: Mammal end

The last line here fails with:

ERROR: invalid redefinition of constant Dog

I suppose I could use Mammal = Union(Dog,Zebra) but this seems not very sustainable.

2 Likes

You have to restart julia to redefine type :slight_smile:

Thanks, I’m already doing that though. The issue is rather that I’m not able to declare that Dog is a subtype of Mammal.

Ahh I skim read the “existing package” part

You can’t redefine an existing type in another package, it’s not a thing. You need to use holy traits instead. You can even do that with existing abstract types, which could be something like:

category(::Animal) = Animal
category(::Dog) = Mammal

somemethod(animal::Animal) = somemethod(category(animal), animal)
somemethod(::Type{<:Mammal}, animal) = ...
somemethod(::Type{<:Animal}, animal) = ... # fallback

I see. I didn’t intend to redefine the type, only to add new information about how the types relate. But, it looks like this is related to the discussion about multiple inheritance. I’m not actually interested in multiple inheritance in this case (turning the type tree into a type lattice), just in ‘stretching’ the existing type tree by interposing a new node that I can stick new branches onto. But, if one were able to declare new supertypes after the fact, it would (in general) allow to create multiple inheritance, which is apparently a difficult feature to implement with multiple dispatch & hasn’t been done yet (might never be done). I’m curious if this simpler case would be easier to implement than the general case, but it’s probably not trivial either.

Thanks for the info about Holy traits. I think this probably good enough for what I am doing.

I imagine it would cause hell for method dispatch and code caching if you could insert types into the hierarchy at will, anywhere in the ecosystem. But that’s a guess.

I’m leaning toward “not causing hell”: any methods that are not aware of the insertion of Mammal would continue to work just fine, since the original supertype (Animal here) would still be a supertype of Dog, so the dispatch should work its way up to the original supertype and then get the original method appropriate for that. However, if I were to specialize an existing function for Animal with a method for Mammal, that would constitute type piracy I think, and could cause real problems.

Ok… but as a random example in practice the code for supertype is:

function supertype(T::DataType)
    @_pure_meta
    T.super
end

So allowing this to change at will will break method purity, and that will have performance consequences.

1 Like