I’m not sure I understand everything in your post, but this is how I’d write you first C++ example:
struct Numbers<IV, FV> { priv iv: IV, // priv so the struct can only be constructed in this module priv fv: FV, } impl<I: Int, IV: Collection<I>, F: Float, FV: Collection<F>> Numbers<IV, FV> { pub fn new(iv: IV, fv: FV) -> Numbers<IV, FV> { Numbers { iv: iv, fv: fv } } } You can also write type aliases with type parameters, but I don’t think you can enforce trait bounds on them afaik: type A<T> = (T, int); ~Brendan On 7 Dec 2013, at 5:10 pm, David Piepgrass <qwertie...@gmail.com> wrote: > Rust newb here. I have theoretical questions. > > Recently I noticed that Higher-Kinded Types (HKTs) have been mentioned on the > mailing list a lot, but I had no idea what a HKT was, or what it might be > good for. After reading about them a little, they reminded me of C++'s > "template template parameters". In C++ you can almost write something like > this: > > template <template <typename> class collection> > struct Numbers { > collection<int> integers; > collection<float> floats; > }; > > So then you can write Numbers<vector> for a structure that contains vector<T> > collections, and Numbers<list> for a structure that contains list<T> > collections. EXCEPT that it doesn't actually work, because vector<T> has two > template parameters (the second one, the allocator, is normally left at its > default). Let's ignore that, though. > > So that brings me to my first question: is this what "higher-kinded types" > means? What is the difference, if any, between HKT and C++ "template > templates"? > > However, as a C++ developer I never actually used a "template template" > parameter because I didn't know they existed for a long time. So instead I > would have written this, which has the same end-result: > > struct VectorTrait > { > template<typename T> > struct collection { typedef vector<T> type; }; > }; > struct ListTrait > { > template<typename T> > struct collection { typedef list<T> type; }; > }; > > template<typename Traits> > struct Numbers > { > Traits::collection<int>::type integers; > Traits::collection<float>::type floats; > }; > // Use Numbers<VectorTrait> for vector<T>, Numbers<ListTrait> for list<T>. > > This is clunkier, but it would have been a bit simpler if C++ supported > templatized typedefs: > > struct VectorTrait > { > template<typename T> typedef vector<T> collection; > }; > struct ListTrait > { > template<typename T> typedef vector<T> collection; > }; > > template<typename Traits> > struct Numbers > { > Traits::collection<int> integers; > Traits::collection<float> floats; > }; > // Now write Numbers<VectorTrait> instead of Numbers<vector>, > // Numbers<ListTrait> instead of Numbers<list>. > > I have found that because of the existence of typedef, "template template" > parameters are never actually necessary; so far, I've never seen a situation > where the typedef-based solution wasn't almost as good. Also, I have found > that "trait" types filled with typedefs seem to be a more general thing than > "template template"; they allow you to do things that would be very difficult > or impossible without them. For example you can use typedefs-in-a-struct to > create circular references among types that don't "know" about each other: > > // I call this a "Combo"; I don't know if the technique has a standard name > struct MyCombo { > typedef ConcreteA<Traits> A; > typedef ConcreteB<Traits> B; > typedef ConcreteC<Traits> C; > }; > template<typename Combo> > class ConcreteA { Combo::B* b; ... }; > template<typename Combo> > class ConcreteB { Combo::C* c; ... }; > template<typename Combo> > class ConcreteC { Combo::A* b; ... }; > > Here I've created a network of types (ConcreteA<MyCombo>, ConcreteB<MyCombo>, > and ConcreteC<MyCombo>) that are linked together through the "Combo" type > MyCombo, so the types can all use each other, but none of the types refer to > each other directly. This design allows you to freely swap in different > implementations of A, B, and C; it has similar advantages to "dependency > injection" or "inversion of control" in languages like Java and C#, except > that the linkages are all defined statically at compile-time, so no dynamic > dispatch is required. > > Without the ability to define "typedefs", this approach is not possible at > all if there is a cyclic relationship. Also, if the combo declares more than > three types, it becomes impractical to specify all those types on the classes > directly as type parameters. > > In C# I learned that this quickly becomes a major problem if you need to > parameterize on more than one or two types. I tried to do "generic" math > (which requires at least two type parameters due to the under-designed > standard libraries) and I also implemented a GiST data structure (see > http://en.wikipedia.org/wiki/GiST), and found out that the lack of any analog > to C++ typedef makes both of those tasks very clumsy, while also making the > code hard to read, because you end up with a rats' nest of type parameters > (or if you omit (otherwise necessary) type parameters, you might use lots of > casts instead.) > > So I guess that leads me to two more questions. > > 2. Does Rust have a "typedef" equivalent that can be used in this way? > 3. Does it make sense to just suggest "just use typedefs instead of > Higher-Kinded Types"? > > _______________________________________________ > Rust-dev mailing list > Rust-dev@mozilla.org > https://mail.mozilla.org/listinfo/rust-dev _______________________________________________ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev