In all these cases with typedefs, you're still implicitly relying on
higher-kinded types, along with what are usually referred to as associated
types:

http://smallcultfollowing.com/babysteps/blog/2013/04/02/associated-items/
http://smallcultfollowing.com/babysteps/blog/2013/04/03/associated-items-continued/

Kinds are basically the types of types. In the normal case there's two
varieties:

 - The base kind, which is the kind of what you normally think of as
"types", i.e. things which can be the type of values. In C++ this is called
typename/class (in Haskell: *).

 - The kind of functions from types of one kind to types of another
(potentially the same) kind; in other words, the kind of types
parameterized over type arguments (of varying kind). The simplest case is
the kind of functions from the base kind to the base kind, in C++
template<typename> class (in Haskell: * -> *).

Then you can iterate the second rule however you like for an infinite
regress of different kinds.

In all of these examples you're using an implicit protocol (type of a
particular kind with a particular name as a member) to encode associated
types. The associated types themselves are of higher kinds. In the Numbers
example, the type parameter itself is the base kind, but has an associated
type with a templatized (function) kind, which is what you're actually
using. In this case you could just as easily skip the middleman and pass
around the higher-kinded type directly, but associated types also give you
the ability to "branch" over the input type, which higher-kinded types by
themselves do not. (This is what happens, for example, when strings declare
that their element type is a character, instead of being parameterized over
an element type.)

Basically, this form of "typedefs" already implies HKT, and if we were to
add HKT, there would be no reason to restrict them to only being associated
types.


On Sat, Dec 7, 2013 at 9:05 PM, Gábor Lehel <[email protected]> wrote:

> Short version: yes, higher-kinded types and template template parameters
> are the same thing. (`template<typename> class` is just one particular
> higher kind; there's also `template<template<typename> class> class` and so
> on, and varying the number of parameters, etc., which you probably know.)
>
> Longer version hopefully upcoming if/when I manage to digest the rest of
> it.
>
>
> On Sat, Dec 7, 2013 at 8:10 AM, David Piepgrass <[email protected]>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
>> [email protected]
>> https://mail.mozilla.org/listinfo/rust-dev
>>
>>
>
>
> --
> Your ship was destroyed in a monadic eruption.
>



-- 
Your ship was destroyed in a monadic eruption.
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to