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

Reply via email to