I kinda disagree with this. This may lead to the following scenario:
- A second package is created to make useful operations over types of the abstract supertype exported by the first package (i.e., the package you describe).
- Some user imports this second package and wants to use it on their own type but their type already subtype an abstract type needed by other interface, or it wants to create a glue package that implements second package interface for a concrete type of a third-party package.
- If the abstract type did not exist, it would only be a question of defining some primitive operations over the type, and letting the second package do its magic.
- However, as the second package expects subtypes of some abstract class, and you cannot add those to an already existing type (or to a type that already needs to have another parent type) you need to create a type wrapper, and the process may become much more annoying.
I agree with the feeling shared by @regier2302, it is something that was already in my mind for some time. I even write these first and second answers to an user with similar but much more applied doubts. I recognize that the type hierarchy is very nice for dispatch on some “set-on-stone” relations like Number (Real
, Float64
, Integer
, Int
, and so on) it creates a great family of fallbacks for operations over generic numbers.
However, in general, my to-go design is to create modules that just describe an interface, without creating any abstract type and without, therefore, dispatching on abstract types. The interface can be separated in primitive functions, for which there is no fallback; fallback functions for which there is a fallback calling primitive functions, but you can also implement your own behavior; and purpose functions that call both previous types and you often do not want to re-implement because otherwise you have no need for the package, the package’s purpose is to provide them (sometimes those will be in a different package, and a Base
package will have both primitive and fallback functions). Then anybody can implement as many interfaces they want for their own types, and glue packages can provide the interface of Package X
to types of Package Y
. Those interface packages can provide some abstract types (or even concrete, in fact), but only to be used trait-style (functions dispatch on them, but they are just a way passing a flag on type-space). The only thing a tree of abstract types adds to this idea is a hierarchy of fallbacks, that sometimes is nice, but at the cost of incompatibilities between interfaces, because a type cannot subtype multiple types and therefore cannot implement multiple interfaces.