I'm running into a pretty subtle issue with using the "family pattern" for higher-kinded polymorphism. I'm a little worried I'm just taking this whole polymorphism thing too far, and I fully expect that the right answer here is to back down and duplicate a little code… but I also get the sense that I might be missing something obvious.
Here's a playground with a fully worked example. But it's kind of long and hairy, so I'll try to introduce it piece by piece here.
The short version is: I have a Wrapper
type that needs to be polymorphic over a polymorphic container type. I'm using GATs and the family pattern to provide that polymorphic type; let's call it Assoc<T>
. I'd like to write an impl
for Wrapper
that applies only in cases when Assoc<T>
implements some other trait (in my example called Meow
).
The long version starts like this: let's first declare three generic structs.
struct One<T>(T);
struct Two<T>(T);
struct Three<T>(T);
All three implement some trait Trait<T>
, for which the details don't really matter. In order to implement my Wrapper
, we use the family pattern to provide the three options for the generic type:
trait Family {
type Assoc<T>: Trait<T>;
}
struct OneFamily;
struct TwoFamily;
struct ThreeFamily;
impl Family for OneFamily {
type Assoc<T> = One<T>;
}
impl Family for TwoFamily {
type Assoc<T> = Two<T>;
}
impl Family for ThreeFamily {
type Assoc<T> = Three<T>;
}
Now we can implement Wrapper
. It has two fields, both of which use F::Assoc<T>
(which is why we need HKT and the family pattern in the first place):
struct Wrapper<F: Family> {
int: F::Assoc<u32>,
float: F::Assoc<f64>,
}
So far so good! However, next let's assume that just two of the underlying polymorphic structs implement some trait, Meow
:
trait Meow {
fn meow(&self) {
println!("meow!");
}
}
impl<T> Meow for Two<T> {}
impl<T> Meow for Three<T> {}
Finally, we return to Wrapper
. I would love to write an impl
for Wrapper
that covers the cases when F::Assoc
is either Two
or Three
but not One
, i.e., that works whenever F::Assoc<T>
is Meow
. It's of course possible to do this for a specific family, like this:
impl Wrapper<TwoFamily> {
fn speak(&self) {
self.int.meow();
self.float.meow();
}
}
…but I can't figure out how to write a single impl
that covers both cases, without copying & pasting the impl
for the two cases. This is not allowed, of course:
impl<T, F: Family> Wrapper<F> where F::Assoc<T>: Meow {}
because T
is unconstrained. (And in any case, if we could do this kind of thing, we wouldn't need families in the first place.)
I keep on thinking there ought to be a way to define a trait SubFamily
that somehow "restricts" the Family::Assoc<T>
associated type so that it is Trait<T> + Meow
, but I can't finagle a way to write that trait.
Thank you for reading to the end of this long and confusing story!