Double check the added paragraph in my previous comment if you missed it.
What I’m suggesting “isn’t HKT”. It is to allow Self to be an ATC (i.e. a parameterized associated type). Effectively:
trait Collection<T> {
type Self<U>: Collection<U> where Self<T> == ImplementingType;
...
}
which I’m writing as:
trait Collection<T> for Self<T> { ... }
with the necessary bounds being implied. Note that there is no HKT (yet), just an ATC. One could imagine using this as:
fn map<T, C_T, U, F>(c: &C_T, f: F) -> <C_T as Collection<T>>::Self<U>
where C_T: Collection<T>, F: Fn(T) -> U { ... }
My observation is that, under this system, for any C_T, there’s exactly one type constructor (let’s call it C) such that C<T>: Collection<T> and C<T> == C_T, because of coherence. If you allowed type constructor parameters, you could therefore rewrite this as:
fn map<T, C, U, F>(c: &C<T>, f: F) -> C<U>
where for<V> C<V>: Collection<V>, F: Fn(T) -> U { ... }
Elision rules could then let you write for<V> C<V>: Collection<V> as C: Collection. Note that Collection itself is not a trait in this model, it’s syntax sugar. However, if you squint, you’ll notice that Collection looks a lot like CollectionFamily, and C looks a lot like CFamily, with all the necessary linking handled automatically. If the elision rules don’t work cleanly, an alternative syntax could be provided, like C<*>: Collection<*>.