I presume you know that subtyping requires that per-class type parameters (e.g. class Foo<A>
) respect some variance rules dictated by the Liskov Substitution Principle (LSP). This doesn't apply to per-method type parameters (e.g. fn foo<A>
). The class type parameters must be declared invariant, covariant, or contravariant (or this can be inferred from their unambiguous usage as follows), which then controls what subtyping is allowed on the class
and in which positions input/output the class type parameters are allowed to appear.
Rust doesn't support any subtyping, but I believe this is problematic for composability because without subtyping it is impossible to populate a Collection<T>
with elements of type T
which are first-class unions (aka disjunctions); thus afaik it becomes impossible to add new types to the union employing the method of the Collection
which appends/inserts an element short of making an entirely new copy of the Collection
. I am very curious to hear how you can handle this in Rust and Haskell without violating DRY boilerplate, because afaik it is impossible. And I suspect this problem may leak into everything. I am awaiting for feedback on how this already handled.
Afaics, the higher-kinds in Haskell are the same as in a language with subtyping, with the only difference being for subtyping the per-class type parameters have a variance declaration and thus can't be used into both input and output locations on a method (but no limitations on functions which are not methods nor on per-method type parameters). Afaik in Haskell at type parameters are always invariant.
As you know and in response to some of the other comments I've read in this thread, afaik the term "higher-kinded type" (HKT) always has the same meaning, which the parametrization of a parametrized type constructor. So if the parametrization of a named (not generic) type is not a HKT. The parametrization of a generic type is a HKT.
Afaik, higher-ranked types (HRT) are multifarious binding of the type parameter separately for each use within a function, instead of one subsumed type binding for all uses.
Edit: afaik it is possible to have subtyping without subclassing. The latter is an anti-pattern because it enables/encourages breaking the invariants of LSP; whereas, afaics the former can be sound and as explained above is necessary for DRY composability.
And example of subtyping which is not subclassing is the example I mentioned above where adding an element of type E
to for example Array<T>
will have a result type of Array<T \/ E>
. Assuming the element type of the Array
is covariant, then we can see that result type is covariant in the output position. Subclassing would be the coercion of MyArray<T>
to Array<T>
. The coercion of Array<T \/ E>
to Array<T>
is not subclassing and only subtyping.