rust-lang / rfcs

RFCs for changes to Rust

Home Page:https://rust-lang.github.io/rfcs/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Higher kinded polymorphism

rust-highfive opened this issue · comments

Issue by tiffany352
Monday Sep 02, 2013 at 01:14 GMT

For earlier discussion, see rust-lang/rust#8922

This issue was labelled with: A-typesystem, I-wishlist in the Rust repository


Rust doesn't support higher kinded polymorphism, despite being a common feature in many functional programming languages. As far as I know, it is a commonly requested feature from people in the FP crowd.

To fully support the idioms that HKP supports we would also need implicits, this would be also useful for passing the execution context to futures for example

Can someone tell the status of higher kinded polymorphism in Rust? When will this feature be added to the language approximately?
I read related issue but nevertheless couldn't find any new information.

No one is currently implementing it, and no one has made a solid specification of what it would look like. If it happens, it will not be for a long time.

Search keywords: higher kinded types, HKT, monad, functor. (cc #1185 (comment))

Why is no one working on this?

commented

Are there any status updates at this time?

@antonkatz not yet. Lots of work is going into implementing the MIR/HIR RFCs, which enable more typesystem features by making the internal representations easier to operate on.

commented

@steveklabnik I wonder what it would look like. Do anyone have any ideas about how the semantics, design, and syntax would be?

Nobody has come up with that stuff yet, no, or at least, no serious proposals.

OK, let's talk about syntax as a light starter then.

Looking at the proposals made so far for Swift in typelift/swift#1, I personally do not find any of them particularly appealing or suitable for Rust.

Currently I would be leaning towards something like the following (using classic Functor/List example):

Trait declaration

trait<A> Functor {
    fn fmap<B>(&self, f: Fn(A) -> B) -> Self<B>;
}

Here Self is inferred to be of kind * -> *, based on the generic parameters to trait, therefore it always needs to have the appropriate number of generics when used in the function signature, so that the kind of the concrete types is always * (in this case, a return type of Self on its own would not be allowed). self is of type Self<A>.

Note that I think this means that this syntax can only express only one "layer" of extra type arguments, and could not easily express kinds higher than that (e.g. * -> * -> *).
Edit: That is incorrect, the syntax can easily support extra "flat layers", just by adding extra generic arguments to trait, e.g. trait<A,B,C> would make Self of kind * -> * -> * -> *.

Also, HKT would only exist in the context of a trait declaration (e.g. a function outside of a trait could not have a return type of kind * -> *, e.g. List); this restricts the scope of the proposal and simplifies the implementation, but we should consider tradeoffs and alternatives.

Trait implementation

impl<A> Functor for List<A> {
    fn fmap<B>(&self, f: Fn(A) -> B) -> List<B> { ... }
}

Cross-pasting from the duplicate issue I opened:

There is some previous discussion about HKTs, type lambdas, type inference, and so on in the comments on the default type parameters RFC.

Given that : at the type level is already taken for trait bounds, here is my basic idea for the syntax of kinds.

The discussion and referenced papers from this Scala issue may also be relevant for us.

commented

trait<A> Functor {

To me this looks confusingly similar to trait Functor<A>. (And what relationship does that A have with the one in the Fn?)

Alternate strawman: trait Functor : for<T> type

Wadler's law in action...

@comex : yes that may be confusing at first, but I think it could make sense:

  • trait Foo { fn bar() -> Baz<A> }: A is a concrete type (this assumes that A is the actual name of a struct, for instance); Baz<A> is always a concrete type.
  • trait Foo { fn bar<A>() -> Baz<A> }: A is a generic type that has to be supplied (or inferred) when calling bar.
  • trait Foo<A> { fn bar() -> Baz<A> }: A is a generic type that has to be supplied (or inferred) when implementing Foo.
  • trait<A> Foo { fn bar() -> Baz<A> }: Foo has kind * -> *; A does not have to be provided in the trait impl; the trait impl abstracts over this type variable, and uses it to collapse the kind of the trait: Self has kind * -> *, Self<A> has kind *.

The pattern here is that, as the introduction of the generic type moves from right to left, the binding of that type parameter to a concrete type happens later and later.

Having a decent syntax for expressing higher kinded traits is important, but I am sure there are more pressing challenges that need to be solved when it comes to figuring out the intricacies of semantics, and how these interacts with the existing features of the type system. Do we need to wait for a more solid, formal foundation for the type system, perhaps post MIR? How does the feature interact with type inference?

How will libraries look once they can take advantage of higher kinded polymorphism? Can we have some interesting examples of what might be possible on the library front to help guide the design process?

@bjz yes I think we definitely need to wait for MIR before we can have a decent idea of how this may be implemented and how it interacts with the rest of the type system, that's why I suggested to start thinking about (or just playing around with) syntax for the time being. If you think this issue is not appropriate to discuss that, I am happy to move the discussion somewhere else.

Good point about providing examples of what would be possible with HKTs that is currently impossible or not ideal, it will be useful also to guide the discussion about semantics once we get closer to that.

Yeah, having a good base of concrete examples might help guide syntax, and figure out desirable semantics. At least it is something that can be done in the mean time, and would really help with eventually crafting high quality RFC.

A fairly simply, but IMHO interesting example is improving the API for iterators: With HKT, we could define the Item associated type to be of kind Lft -> Type, and then have

fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>

This flexibility is needed, e.g., when writing a safe Doubly-Linked-List with Rc and RefCell, and I also recently wrote a blog post that defined a "stream" API that was essentially this, except that it had to work with current Rust as thus ended up being rather convoluted... Here's the crate doc http://burntsushi.net/rustdoc/fst/trait.Streamer.html. The blog post that linked there is http://blog.burntsushi.net/transducers/.

commented

One thing that's important is consistencies. HKT is a very powerful concepts, and will probably be used a lot when (if?) they're introduced. Having syntax and semantics which is consistent with Rust's type system is important for such a big change.

Another subject to discuss is how such a change will affect the libraries. How will libstd look with HKTs?

commented

My humble opinion is that HKT is what brings Rust into a whole different league. Rust might just become a contender in the functional programming community.

In general, higher-kinded polymorphism allows functions to be generic over a type constructors. And rust has many type constructors. :)

As @RalfJung said, they allows functions to be generic over the specific pointer management technique, which suggests they might simplify the contentious situation around allocators.

In Haskell, HTKs appear pretty central to handling errors with type constructors, so they might help simplify exception looking constructs in Rust too.

Also, it appears HKTs would improve dealing with Rust's different types of closures too, which I suspect is what @antonkatz means.

commented

@ticki you're right. I probably don't.
I've looked at learning Rust, but this was a deal breaker. I don't often use HKTs, but once in a while, I encounter a problem that can be solved very nicely with HKTs.
What I meant by my comment, is that it's hard to ask a functional programmer to write code without HKTs.

commented

@antonkatz Right, HKTs might render big parts of the API obsolete, so sure adding HKTs is a big thing. But they provide a really nifty abstraction allowing very clean API. But I do believe the change is worth it.

So sure, adding HKTs is a drastic move, but due to the power of them, it's worth the price.

commented

What I meant by my comment, is that it's hard to ask a functional programmer to write code without HKTs.

I see. I must have misunderstood the comment.

commented

I experience the lack of HKT mostly for lifetimes; I would be very happy with just a restriction that enables types to be higher-kinded over just lifetime params.

These are needed for encoding read-only or read-write iterators into a trait in a way that's composable (can recursively pass down a value and interchangeably iterate it and mutate it).

HKTs might render big parts of the API obsolete

We kept HKT in mind when stabilizing libstd, so hopefully this is not true, or at least, on a big scale.

commented

@nikomatsakis has some interesting thoughts about HKTs here: #213 (comment)

Just chiming in on syntax because I've been running into HKT struggles lately. I think that it might be desirable to show HKT as type functions explicitly. Examples:

// HashMap<K, V> reads as "HashMap has a K, V" whereas |K, V| Map reads as "K, V completes a Map"
trait |T| Monad {
    fn return(obj: T) -> Self<T>;
    fn apply<U>(&mut self: Self<Start>, f: F) -> Self<U> where F: FnOnce(T) -> Self<U>;
}

// |T| Option<T> reads as "with T, we can create an Option with T"
impl Monad for |T| Option<T> {
    fn return(obj: T) -> Option<T> { Some(obj) }
    fn apply<Start, End, F>(&mut self: Self<Start>, f: F) -> Option<End> where F: FnOnce(Start) -> Self<End> { self.and_then(f) }
}

trait Iterable {
    // with a lifetime, we can create an Iter
    type |'a| Iter;
    fn iter<'a>(&self) -> Self::Iter<'a>
}

impl<T> Iterable for Vec<T> {
    // an iter is a function which takes a lifetime and returns a vec::Iter<T>
    type Iter = |'a| vec::Iter<'a, T>;
}

fn apply_twice<M: Monad, A>(start: M<A>, f: F) where F: Fn(A) -> M<A> {
    start.apply(f).apply(f)
}

fn iterate<I, T: Iterable<Iter=I>>(val: &T) where <I as Iterator>::Item: Debug {
    for item in val.iter() {
        println!("iterating: {}", item);
    }
}

fn main() {
    let val = Option::return(5);
    assert_eq!(apply_twice(val, |x| Some(x + 1)), Some(7));
}

Note that the |T| syntax uses the same syntax as <T>, besides the surrounding characters. This means that it also supports bounds. Additionally, type parameters which are higher-kinded are written using the |U| V syntax, if I can find a reason why we need those. (e.g. fn thing<|U| V>)

Also haven't thought about how HKT trait objects would work at all. I considered type Iter<'a> but I feel like making HKT look alike with each other and distinctly different from other types is a plus.

For now, the syntax doesn't support simplified lambdas (i.e. |T| Option<T> as Option) due to the number of weird rules with elision and default parameters, and the fact that we don't know how to refer to the argument type, but there may be solutions to add that in after the fact. I feel like any system would fundamentally have that problem, though.

I'd agree that |T| meshes better with @nikomatsakis's concerns about currying and lifetimes #213 (comment)

I hope Rust avoids the functor morass in OCaML and goes directly for HKTs. It worth checking out section 1.1 here https://ocamllabs.github.io/higher/lightweight-higher-kinded-polymorphism.pdf and maybe https://github.com/ocamllabs/higher

commented

I don't like the |T| syntax, because it is easy to confuse with closures.

I also like the idea of different syntax for type variable introduction |T| and consumption <T>, and it looks like it will indeed make it easy to express HKT in some cases.

@ticki the point is exactly that it mimics the syntax used to introduce arguments in closures, but in the context of type signature only individual type variables may be contained, so there should be no risk of confusion.

Appears HKT in https://gist.github.com/14427/af90a21b917d2892eace meets @nikomatsakis's restriction that the higher-kinded type be a partially applied type, right? It's maybe worth patching it or similar with more explanatory type variable names and writing up a version using the trait |T| ... syntax, or the trait<T> ... syntax if that's your shade.

As an aside, I have not yet really understood the problems that come from using the mere presences of unbound type variables to denote higher-kinds like in Haskell https://hackage.haskell.org/package/base-4.8.1.0/docs/src/GHC.Base.html#Functor I donno if that "[tethers] the compiler's type inference .. to trait resolution". Ignoring that, couldn't one impose the partially applied type restriction at the impl site somehow without necessarily asking for explicit type variables? It's maybe an issue that Rust already assigns a meaning to lifetimes being unbound elsewhere.

commented

As an aside, I have not yet really understood the problems that come from using the mere presences of unbound type variables to denote higher-kinds like in Haskell

There are two problems with that approach: First of all, this is prone to bugs. If you forget to bind some parameter, you can get problems (though, you'd probably for the most time get compile errors, and not mysterious behavior). Secondly, you cannot define bounds for the free parameters, which takes away the power from HKTs.

Secondly, you cannot define bounds for the free parameters, which takes away the power from HKTs.

Is this true? Haskell has HKTs but AFAICT no bounds on the "free" type variables (in fact those are not even present in the typeclass declaration, and the kind is normally also inferred by the compiler).

For reference, in Haskell:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

class Applicative m => Monad m where
    (>>=) :: forall a b. m a -> (a -> m b) -> m b
    return :: a -> m a

Afaik, there is no need for bounds on the type variables that give a type a higher-kind. There are bounds on the higher-kinded type itself, expressed by the =>, but expressing bounds on it's variables sounds incorrect. Afaik bounds on the variables could be expressed later, like through the choice of implementations of the class/trait.

Also, there is no real concern about bugs because if you leave the type variables unbound by mistake then you'll always get a type error from the type not being applied correctly. In fact, there is no worry about "[tethering] the compiler's type inference .. to trait resolution" even, because the unbound type variables get applied in the definition.

In other words, one could detect the higher-kind from the appearance of Self<T> in

trait Monad {
    fn return<T>(obj: T) -> Self<T>;
    fn apply<Start, End, F>(&mut self: Self<Start>, f: F) -> Self<End> where F: FnOnce(Start) -> Self<End>;
}

It'd be a type error if either just Self or Self<T,U> appeared in the same trait as Self<T>.

There is still the issue of formalizing this partially applied type restriction, which admittedly I do not yet fully understand, but it sounds like a restriction on impls. In particular, I doubt one could eliminate the |T| from :

impl Monad for |T| Option<T> {
    fn return<T>(obj: T) -> Option<T> { Some(obj) }
    fn apply<Start, End, F>(&mut self: Self<Start>, f: F) -> Option<End> where F: FnOnce(Start) -> Self<End> { self.and_then(f) }
}

In both snippets, we bind all type variables in the fn lines, not the trait or impl lines, since the trait having a higher-kind actually means it's independent polymorphism of the functions, not polymorphism of the implementation as a whole. Haskell is less verbose here, but afaik the function still gets the quantifier. Rust always makes these quantifiers explicit.

Also, impl Monad for |T,R| Meow<T,R> ... is illegal because Monad has Self<T>, not Self<T,U>. And impl Monad for Option<String> ... is similarly forbidden.

In addition, the partially applied type restriction forbids at least stuff like impl Monad for |T| T { ... } but not sure how far that goes. I suppose impl<E> Monad for |T| Result<T,E> { ... } works fine.

Interestingly, you actually could add bounds to the fn lines in the trait definition, which merely limits your impls, so maybe it's unnecessary. I think the more interesting scenarios are when you must apply new bounds in the impl, like maybe M is a Monad only when applied to Send types or only when some lifetimes workout a particular way.

https://m4rw3r.github.io/rust-and-monad-trait/ Just linking this here because it talks fairly succinctly about the same issues that I ran into whilst playing with 14427's pseudo-HKT gist from above.

Posting to watch. :)

@pthariensflame There's a subscribe button as well...

@jplatte But it doesn't cause the thread to show up in the “Participating” tab!

But you aren't participating!

commented

Now, he/she is. 😉.

I wrote a comment about the need to exclude FnMut/FnOnce from higher-kinded traits, at least initially: https://m4rw3r.github.io/rust-and-monad-trait/#comment-2532714842

An important use case for higher-kinded types would be simultaneously supporting both single-threaded performance with Rcs vs thread safety with Arcs and Send bounds.

It appears that gj and eventual_io are separate projects in part to provide this separation, which represents some fragmentation in Rust's asynchronous IO ecosystem.

commented

@burdges Yes, that is a very important point. Often you can choose between limiting your libraries scope to single-threading, or sacrificing performance in single-threaded applications. Neither of these should be mutually exclusive with a sufficient type system.

Just for future reference, what Scala's doing right now scala/scala#5102 may be of interest to us (see also very grokkable explanation).

Though it seems like this specifically wouldn't solve the kind of difficulties Rust has prepared for itself by putting the parameters of Result in the order they are in (which is the specific example from the above gist) and default type parameters at the end of type parameter lists.

Now that MIR is turned on by default, i'm curious if it's now possible to implement HKT in rust? This is the last piece I and many others are waiting on before making the plunge into rust!

@tbenst MIR was a hard blocker on some features having to do with lifetimes, but not higher kinded polymorphism as far as I know. The reason Rust does not currently have higher kinded polymorphism has more to do with a desire to be intentional & careful about how the type system is extended.

Being honest, I hear the opinion you express - that some people don't want to use Rust until it supports this feature - much more often than I see current users struggling with a lack of expressiveness because it isn't present. The one exception is associated type constructors with lifetime arguments, which is why I wrote #1598.

HKTs would bring a lot of typed FP folks to Rust. It's a must feature if you want to encode abstractions once and necessary for most encoding of typeclasses and techniques associated to typed FP. It also opens the door to powerful typelevel generic techniques. Please say yes to HKTs :)

@raulraja You don't have to convince the Rust community of the benefits of HKT. ;-) The reason they aren't there yet is that other features have had higher priority, and that there are a lot of detail to be worked out to make sure that things actually work properly. No HK may be liming, but "bad" or even unsound HKT would be worse.

I do not necessarily know what I'm talking about here, but.. It seemed unclear if HKTs are "one thing" in the same way they might seem to be in say Haskell : In Rust, the first big applications for HTKs are things around polymorphism over fancy pointer types that most languages with HKTs solve by boxing everything. Monads seemingly fall into categories that require different implementations /specialization. And other things like that. I think one scary aspect of HKTs is library authors running off and using them in non-zero cost ways, so if anyone could "classify" the different ways that HKTs should be instantiated then that might help. I do not know enough to do this, so take anything I say with a grain of salt. :)

@burdges Scala examples but nonetheless explanatory of the importance of HKTs http://typelevel.org/blog/2016/08/21/hkts-moving-forward.html

@raulraja you missed the point that @burdges and others have been making. Yes, everyone here including Rust language developers already knows the benefits of HKTs and what they can be used for. The problem is to integrate this well with Rust's other features.

If you want to help improve the situation, you need to study how Rust's other features interact with HKT. The link you posted is below and before this level; it does not help to solve these problems, nor does it educate us on how to solve these problems. Repeatedly posting about what everyone already knows, is not useful. Other people joining this thread, please also do some reading before you post.

@infinity0 the link I posted is what many folks would expect from HKTs, I'm pretty sure is useful for some folks thinking about introducing this feature to the Lang. When you search for rust HKTs you land here. That is how I found this conversation. I don't know about the internals but you seem to be implying I should learn them to express my desire to have HKTs in rust. I obviously don't. Feel free to remove my post if you have the authority or think it's out of place in this forum. Also I recommend you read up on manners when addressing newcomers to this community as my intention was only to stress the importance of this feature to the FP programming community at large and not how to best implement it in rust. Many folks come here and don't see the obvious advantage of such a feature. That link will shed some light. @burdges my apologies for misunderstanding you.

At least in the ? as do notation discussion, it felt to me like all the monads I knew about fell into a couple categories that needed different flow control implementations. The first was the carrier trait version of ? for Result plus some sort of state. You could for example build a transactional monad in that set up. The second was some form of collections, so like if you continued using ? then it would denote a for loop over an iterator or something. In say Haskell, some of these things would be handled by building up closures, which seems non-zero cost.

Anyways, there are two recently accepted RFCs for more limited subsets of HKT :

First, impl Trait improves polymorphism over unboxed return types. And futures-rs already recommends using them to write code that's polymorphic over future types.

Interestingly doing impl Trait for trait fns gives HKTs, so maybe a useful exercise would be working out what HKT patterns would be non-zero cost if implemented using some hypothetical impl Trait syntax for trait fns in some naive way. I'd suspect collection monads fit the bill, which might've implications for iterators, although maybe something simpler.

Second, associated type constructors are HTKs with only a lifetime parameter, which improves borrowing when using trait fns.

@raulraja thx I think we are of the same mind. @infinity0 @burdges glad to hear that the rust community recognizes the value of HKT and that we can move past this part of the discussion.

It looks like rust already has many of the pieces needed for HKT: generics, closures, function typing, etc. I'm not familiar with the rust internals but we can discuss how the following function might be implemented and go through the type checker?

def compose(*functions):
   """return a functional composition of given functions.

    compose(f,g,h)(x) is equivalent to h(g(f(x)))"""

    return functools.reduce(lambda f, g: lambda x: g(f(x)), functions)

For context:

I do neuroscience research and use the following function as the key foundation to analyze 1000s of neurons concurrently. I'm interested in rust as it is low-level and more suitable than python for real-time processing with consistent memory usage and runtime performance.

def apply_pipeline(pipeline, units):
    """Apply the function pipeline to the spike train (List[float]) of each neuron."""
    return {k: pipeline(v.spike_train) for k,v in units.items()}

This is particularly powerful when combined with function factories (connoted by 'f_'):

def f_filter(function):
    def filter_dict(f,d):
        for key, val in d.items():
            if not f(key,val):
                continue
            yield key, val

    def anonymous(x):
        if type(x) is list:
            return list(filter(function, x))
        elif type(x) is dict:
            return {k:v for (k,v) in filter_dict(function,x)}

    return anonymous

def f_has_stimulus_type(stimulus_type: Union[str,List[str]]):
    if type(stimulus_type) is str:
        stimulus_type = [stimulus_type]
    def anonymous(e):
        if (e["stimulus"]["stimulusType"] in stimulus_type ):
            return True
        else:
            return False
    return anonymous

The typical workflow uses compose to construct a pipeline, which is then used repeatedly. I've included actual functions to illustrate how meta-parameters (Edit: changed to also show passing a function as an argument to f_filter) can be passed to modify a function, but their actual definitions are irrelevant for the purposes of this example.

get_ifr_by_stimulus = compose(
    f_create_experiments(stimulus_list),
    f_instantaneous_firing_rate(sigma=1,bin_width=0.001,bandwidth=0.1),
    f_filter(f_has_stimulus_type(["GRATING", "BAR'])),
    group_by_stimulus(),
)

firing_rate_1 = apply_pipeline(get_ifr_by_stimulus, data1)
firing_rate_2 = apply_pipeline(get_ifr_by_stimulus, data2)
...etc...

Interestingly doing impl Trait for trait fns gives HKTs

That's a red herring: it's not the same feature as the accepted RFC, but rather sugar for associated types:

trait Foo {
    fn foo<T>() -> impl Trait;
}
// is sugar for:
trait Foo {
    type Assoc<T>;
    fn foo<T>() -> Self::Assoc<T>;
}

This is a non-essential feature, and getting it before higher-kinded associated types has all the risks of adding HKT to Rust, with an extra layer of obscurity which will cause soundness issues to lurk for longer.

@tbenst That has absolutely nothing to do with HKT:

fn compose<T, U, V, F: FnMut(T) -> U, G: FnMut(U) -> V>(mut f: F, mut g: G)
                                                        -> impl FnMut(T) -> V {
    move |x| g(f(x))
}

Making it variadic requires VG (variadic generics), but that's also orthogonal to HKT.

What you're doing seems to involve iterators anyway, so you might want combinators for those instead.

@tbenst like eddyb, I don't see anything in your post that involves higher kinded polymorphism. I think it would be to your advantage to try Rust, it may have everything you need already. :-) And if it is missing something, feedback about where you are actually running into the limits of the type system is much easier to take action on.

@eddyb awesome, thanks for the example! Admittedly I don't yet understand what you wrote but looks like I have no longer have excuse to delay learning rust ;).

@withoutboats f_filter is a trivial case of polymorphism that operates on both lists and dictionaries, returning a value of the same type. Thus, a downstream function could fail depending on input if it only operates on, say, a list. I make these types of mistakes from time to time in python since there is no type checker. Although it sounds like the rust type checker would already catch this.

@tbenst Rust's filter is already implemented on Iterator, which is a generic trait that allows you to iterate over many kinds of things, including both vectors and hash maps. Not only are the kinds of errors you're describing caught by the type checker, but Rust's standard library also provides some very compose-able adapters so you wouldn't need to write something like filter_dict.

Since some seem to be wondering what HKTs are ...

Let's talk about Kinds, baby

For the uninitiated, I'll try to do a brief overview of what kinds, and HKTs (Higher Kinded Types) are, and a few choices to consider for HKTs in Rust. Hopefully this can shed some light on the topic for some. Understanding this might require some knowledge of haskell, but I'll try to give Rust equivalents where possible.

What is a Kind?

Kinds can most succinctly be described as "types of types". In this sense, they are higher-order-types.

NOTE: To get the kind of something in ghci, you can use :k <type>, where <type> is the one you want kind information about.

Let's take a look at the most basic kind: *, pronounced: star - which is the kind of all data types, and can also be called "proper types", or the concrete kind. Just as we have constructors for data types, such as:

data Nat = Zero | Succ Nat

-- where

λ> :t Zero
Zero :: Nat
λ> :t Succ
Succ :: Nat -> Nat
λ> :k Nat
Nat :: *
// in Rust - this is a truly inefficient way to implement Nat, but simple to grasp.
enum Nat { Zero, Succ(Box<Nat>) }

, we also have type constructors that take types as arguments and produce another type as a result.
The star kind can thus be seen as a type constructor taking zero arguments, i.e: a nullary type constructor.

Next, if we add generics to the mix, we get other results:

data Maybe a = Nothing | Just a

λ> :k Maybe
Maybe :: * -> *
// See: https://doc.rust-lang.org/std/option/enum.Option.html
pub enum Option<T> {
    None,
    Some(T),
}

As we see, a Maybe, is a unary type constructor from a type (of an element in the Maybe, in this case) to a specific Maybe type, such as Maybe Nat, which has kind star, as seen in: λ> :k Maybe Nat :: *

If we add another generic parameter, we get a binary type constructor. Such an example is the kind of the function type, or the kind of Either (Result in Rust), or a Map (similar to BTreeMap in Rust):

λ> :k (->)
(->) :: * -> * -> *

data Either a b = Left a | Right b
λ> :k Either
Either :: * -> * -> *

λ> :k Map
Map :: * -> * -> *

An aside note: to actually create a Map, the key type must satisfy the Ord trait or type class.

In GHC, there are other kinds which are of no interest, but GHC.Prim.Constraint is of special significance.
This is the kind that you get when you fulfill a type class, which correspond to traits in Rust.
For example, for the Eq type class (Rust equivalent to Eq):

λ> :i Eq
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

λ> :k Eq
Eq :: * -> GHC.Prim.Constraint

In this sense, in type theory, both type classes and traits are in fact types.

Higher Kinded Types

So far, everything I've covered is possible in Rust.
But wait, there is more - enter HKTs (Higher Kinded Types).

Higher Order Functions

Just as there are functions taking functions as arguments (or returning functions), which are called HoFs (Higher Order Functions).

Some well known HoFs are: map, fold, filter, (.).

twice :: (a -> a) -> (a -> a)
twice f = f . f
// in Rust:
fn twice<A, F>(function: F) -> Box<Fn(A) -> A> 
    where F: 'static + Fn(A) -> A {
    Box::new(move |a| function(function(a)))
}

Higher Order Type-Constructors

Just as there are HoFs of regular types, there are HoFs of kinds, for example: (k1 -> k2) -> k3

Examples of type classes with that form are:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

-- fully expanded:
class Functor (f :: * -> *) where
    fmap :: (a -> b) -> f a -> f b

λ> :k Functor
Functor :: (* -> *) -> GHC.Prim.Constraint

-- An example instance of Functor for Maybe:
instance Functor Maybe where
    fmap f Nothing  = Nothing
    fmap f (Just a) = Just (f a)
class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

λ> :k Applicative
Applicative :: (* -> *) -> GHC.Prim.Constraint

-- An example instance of Applicative for Maybe:
instance Applicative Maybe where
    pure x = Just x
    Nothing  <*> m = Nothing
    (Just f) <*> m = fmap f m
class Applicative m => Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

λ> :k Monad
Monad :: (* -> *) -> GHC.Prim.Constraint

-- An example instance of Monad for Maybe:
instance  Monad Maybe  where
    (Just x) >>= k = k x
    Nothing  >>= _ = Nothing

These type classes allow us to reason about data structures as such (assuming they are instances of the type class). Here, f and m are as expected generic data structures, or unary type constructors of kind * -> *, allowing us to be generic over f t, where t is a type, for example: f a, f b.

More about monads: http://dev.stephendiehl.com/hask/#monads

FlexibleInstances

FlexibleContexts

TypeSynonymInstances

MultiParamTypeClasses

Consider the case where we want a polymorphic way to convert between data structures, but preserve the elements. How do we encode this?

-- To enable the language feature.
{-# LANGUAGE MultiParamTypeClasses #-}

-- Needed for the Either () instance below:
{-# LANGUAGE FlexibleInstances #-}

-- Needed to be explicit about f, g :: * -> *
{-# LANGUAGE KindSignatures #-}

import Data.Monoid ((<>)) -- for <> operator.

-- Converting between data structures while preserving elements.
class StructureConversion (f :: * -> *) (g :: * -> *) where
    convert :: f a -> g a

-- Maybe and Either () are isomorphic.
instance StructureConversion Maybe (Either ()) where
    convert Nothing  = Left ()
    convert (Just a) = Right a

-- A binary tree:
data BTree a = L | N a (BTree a) (BTree a)
    deriving Show

-- Converting between BTree:s and Lists.
-- NOTE: this is not structure preserving.
-- i.e: converting to list and then back could never guarantee the exact form back.
instance StructureConversion BTree [] where
    convert L         = []
    convert (N e l r) = e : convert l <> convert r

Using the definitions:

λ> convert (Just 42) :: Either () Int
Right 42

λ> let tree = N 1 (N 2 L L) (N 3 (N 4 L L) L) :: BTree Int
λ> convert tree :: [Int]
[1,2,3,4]

FunctionalDependencies

When dealing with type classes with multiple parameters, a functional dependency between the parameters a -> b, means that a uniquely determines b - which means that a1 ≡ a2 -> b1 ≡ b2

An example of such a type class is MonadState:

class Monad m => MonadState s m | m -> s where
    get   :: m s
    put   :: s -> m ()
    state :: (s -> (a, s)) -> m a

Which has kind:

λ> :k MonadState
MonadState :: * -> (* -> *) -> GHC.Prim.Constraint

Not only type classes involve HKTs

An example where data types involve HKTs is StateT (the state transformer monad), which looks like:

newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }

instance Monad m => MonadState s (StateT s m) where
    get     = state  $ \s -> (s, s)
    put   s = state  $ const ((), s)
    state f = StateT $ return . f

instance Monad m => Monad        (StateT s m) where
    return           = state . (,)
    (StateT f) >>= g = StateT $ f >=> \(a, s1) -> runStateT (g a) s1

instance Monad m => Functor      (StateT s m) where
    fmap = liftM

instance Monad m => Applicative  (StateT s m) where
    pure  = return
    (<*>) = ap

And has the kind:

λ> :k StateT
StateT :: * -> (* -> *) -> * -> *

More complex HKTs

Here are some examples of more complex HKTs.

  • MonadTrans, kind: ((* -> *) -> * -> *) -> GHC.Prim.Constraint
class MonadTrans t where
    -- | Lift a computation from the argument monad to the constructed monad.
    lift :: (Monad m) => m a -> t m a

instance MonadTrans (StateT s) where
    lift m = StateT $ \si -> (, si) <$> m
  • Category, kind: (* -> * -> *) -> GHC.Prim.Constraint
class Category cat where
    id :: cat a a
    (.) :: cat b c -> cat a b -> cat a c
class IMonad m where
    ireturn :: a -> m i i a
    (>>>=) :: m i j a -> (a -> m j k b) -> m i k b

A note on syntax

In my opinion, one of the really nice things about type classes in haskell is that they are incredibly terse - as opposed to verbose. While the same terseness is not possible in Rust, it should still be an aim to not let things be overly verbose.

What should be available in Rust?

I believe it is a good idea - as others have put it - to find examples where HKTs are useful in Rust in a mostly zero-cost way. But I can't say I have anything special to contribute here. It might also be good to classify these into the various kinds they have, so that we know the needs of the type system... But it might also be a good idea to start from the kinds and then see if anything useful can be made that have those kinds.

  • Should HKTs be possible in structs - which are not traits, as in the StateT example?
  • Should MultiParamTypeClasses (MPTC) and FunctionalDependencies be possible?

MPTCs means that we can't really talk about a Self anymore, since they are rather relations between two or more type constructors. Having MPTCs would therefore probably mean that something different than trait is needed. Here we see that traits and type classes diverge in semantics as traits give you trait objects. The keyword trait could possibly be reused, but in this case, I find it difficult to see how it would be possible to omit the type constructors f, g in class StructureConversion f g - especially not in the case of FunctionalDependencies.

  • Should FlexibleContexts and FlexibleInstances should be possible?

I'd like to note that Rust already has multi-parameter type classes, under the name "traits with (additional) type parameters."

@pthariensflame I'm guessing you are referring to traits such as:

trait ConvertTo<Output> {
    fn convert(&self) -> Output;
}

https://doc.rust-lang.org/book/traits.html#where-clause

While this is true, Output has in this case kind *, which is different than multi-parameter type classes with HKTs.

@Centril What's different between Self and Output having a kind other than type, syntax aside?

@eddyb
Nothing I guess. You could allow for Output<T> to make Output a type constructor of kind * -> *, just like for Self<T>. Then:

class StructureConversion f g where
    convert :: f a -> g a

would become (using @burdges syntax):

trait StructureConversion<Output> {
    fn convert<A>(&self: Self<A>) -> Output<A>;
}

Functional dependencies would be covered by associated types, so:

class Monad m => MonadState s m | m -> s where
    get   :: m s
    put   :: s -> m ()
    state :: (s -> (a, s)) -> m a

becomes:

trait MonadState: Monad<Self> {
    type State;
    fn get() -> Self<Self::State>;
    fn put(state: Self::State) -> Self<()>;
    fn state<A, F>(f: F) -> Self<A>
        where F: FnOnce(Self::State) -> Self<(A, Self::State)>;
}

Absolutely no MPTCs. They are an abomination to work with in the internals of GHC and in practice. They make infinitely more problems then they are worth (looking at you overlapping instances and the many solutions to them). Keep rust free of MPTC and instead focus on improving associated types or in Haskell terms type families.

@mckeankylej I'm confused by your comment.

You're using very specific Haskell jargon, so I'm not sure I actually understand what feature you're talking about. But I looked up "MPTC" and found out it stood for "multi-parameter type class," or, in Rust terms, a trait with type parameters in addition to the Self type. If this is the feature you mean, I am very confused, because:

  1. Rust has traits with multiple type parameters, and has had them for years - since before it became stable and before it gained associated types. Most of the operator traits are multi-parameter, for example.
  2. I don't know what multi-parameter traits really have to do with higher kinded polymorphism. If anything the issue is multi-parameter type constructors, because it introduces a requirement for either partial application or currying.
  3. In Rust, at least, impls of a trait with only a single parameter can easily overlap, multi-parameter traits are not the cause of this issue.

I can only concur with @withoutboats ... I'm a Haskeller and my (current) thinking is that MPTC are entirely uncontroversial. (Not that MPTCs have much to do with HKTs, at least as far as I understand it)

I vaguely agree with this sentiment, though:

focus on improving associated types or in Haskell terms type families.

I think adding more flexibility in the associated items of a trait is more important than adding more flexibility in parameter kinds. For example, I often want higher kinded associated items, but rarely want other forms of higher kinded polymorphism. I want associated consts to be stabilized more often than I want const parameters. I'm not saying more parameter kinds can't be beneficial, but I've found associated items to really be 'where its at' in my experience.

As established by @pthariensflame, Rust already has MPTCs, with one example being the From trait.

What Rust lacks atm is the ability to define type constructors of higher kindedness - i.e: (k_1 -> k_2) -> k_3 as opposed to * -> ... -> * - and this doesn't just apply to traits - maybe it would be useful with higher kindedness with structs as well.

For consistencies sake - if higher kindedness is added to the language, then it should be supported for MPTCs as well, i.e: more than for Self<T_1, .., T_n>, so also: Output<T_1, .., T_n>. Associated types is another way of providing MPTCs as you can see in my MonadState example above...

I believe it is a good idea - as others have put it - to find examples where HKTs are useful in Rust in a mostly zero-cost way. But I can't say I have anything special to contribute here.

One important example I've come up against is parameterizing structures over different smart pointer types. Particularly at the library level, it is quite useful to have structures which use some kind of reference counting (Rc or Arc) but don't particularly care which. Allowing the user to choose gives us the best of both worlds: cheap reference counting for single-threaded scenarios, and atomic reference counting when Send + Sync are required.

As for avoiding Haskell-style MPTCs, Rust's concept of "Kind" could be based on a sort of "trait signature" as opposed to a function from types to types. For example, you could have the kind of traits with N parameters and M type members.

@JohnColanduoni I know I've used this kind of parametrizing in C++ in order to implement trait object alikes, and I'd like to be able to do the same thing in Rust.

Crazy idea, taking the lead from the Fn traits.

The following are builtins:

trait Type {}
trait <impl Type> -> impl Type {}
trait <impl Type, impl Type> -> impl Type {}
...

These are automatically implemented by the compiler like so:

impl Type for () {}
impl Type for i32 {}
...
impl <impl Type> -> impl Type for Option {}
impl <impl Type, impl Type> -> impl Type for Result {}
...

We also have type level closures:

type MyResult = |T: impl Type| Result<T, MyError>;
type MyI32Result = MyResult<i32>;

We now can define:

trait Functor: <impl Type> -> impl Type {
    fn map<T, U>(self : Self<T>, f: impl Fn(T) -> U) -> Self<U>;
}

impl Functor for Option {
    fn map<T, U>(self : Option<T>, f: impl Fn(T) -> U) -> Option<U> { ... }
}

impl<E> Functor for (|T| Result<T, E>) {
    fn map<T, U>(self : Result<T, E>, f: impl FnMut(T) -> U) -> Result<U, E> { ... }
}

This sacrifices 'prettiness' in order to stay try to keep it consistent with Rust's existing concrete syntax, semantics, and idioms. For example, the impl Types are horribly verbose - but I needed them to stay true to how the trait syntax works... :/

Came across kinder, thought it might be of interest to you if you hadn't seen it already. From the README:

Algebraic structure and emulation of higher-order types for Rust

I was thinking about this for some time and reached the conclusion that generics of a higher kind requires some kind of generic that is not monomorphised at compile time, ie. if you need to know the size of an object at compile time you can't really do it. Must be the reason why the languages that support this have some sort of runtime. Please correct me if I'm wrong.

@larroy Many functional languages have HKT, even C++ has template templates. It doesn't require RTTI, they are monomorphized at compile-time.

I was thinking about this for some time and reached the conclusion that generics of a higher kind requires some kind of generic that is not monomorphised at compile time, ie. if you need to know the size of an object at compile time you can't really do it. Must be the reason why the languages that support this have some sort of runtime. Please correct me if I'm wrong.

Types using higher kinded polymorphism can be monomorphized. On the other hand, I think higher rank types can't be monomorphized. On the other other hand, higher rank trait bounds with type parameters are fine. So its specifically a type like for<T> fn(T) -> T.

There are other people who would know better than me though.

@withoutboats With fn being a function pointer in Rust, perhaps Box<for<T> Fn(T) -> T> fits better?
It's equivalent to having a trait with a generic method, which is already banned from trait objects.
Rust could generate RTTI-style code for that, I suppose - well, you can cheat with function pointers too, so maybe I'm making an unnecessary distinction. I agree either way with the premise.

@larroy @withoutboats Implementing higher-rank types (and polymorphic recursion) requires no RTTI. You just have to make sure values of the quantified type are behind pointers, like Rust already does with exotically-sized types or trait objects. So we can say for<T> fn(T) -> T is illegal but for<T> fn(&T) -> &T, for<T> fn(Box<T>) -> Box<T>, etc. are fine.

@jmegaffin That's interesting; how would they be represented?

I'm not sure its worth the complexity cost but its cool.

@jmegaffin Keep in mind that the existence of unsized types in Rust actually screws up that scheme in its naïve form, because of fat pointers.

@withoutboats Not considering the fat pointer issue, pointers are fundamentally untyped. Thus, it would be represented exactly as written, plus pointers to vtables for any non-marker traits T is constrained to implement. Again, that doesn't take fat pointers into account.

@pthariensflame Yeah, I suppose it works for e.g. for<T: Sized> fn(&T) -> &T but there needs to be more to it otherwise. There's always the possibility of adding an extra level of indirection (for<T> fn(&&T) -> &&T) but that doesn't strike me as being particularly satisfying.

Perhaps it was a mistake for exotically-sized types to bubble up information to their pointers. It's not strictly necessary, after all.

It's not enough to make sure they're behind pointers in the fn signature, you also have to make sure they're not used in any way that's not behind-a-pointer in the function body either (copied to the stack, calling another function that's not "only-behind-pointers-safe", etc.). Unless you handle size/alignment completely dynamically...

@glaebhoerl I don't see any issue with doing it dynamically. Performance will be a little worse, of course, but that's unavoidable. It's only not worse in languages like Haskell because they don't do any monomorphization in the first place.

If you want to use a generic function polymorphically, you just compile it to be dynamic where necessary. Any generic functions that are called that depend on a polymorphic type must in turn be compiled to be dynamic and then used at that call site. That should be enough.

It sounds like we'd have to restrict this to object safe bounds and represent this the same as trait objects - that is for<T: Read> fn(&T) would be no different in representation from fn(&Read). In which case I don't see any expressive difference from trait objects either.

@jmegaffin Wait. If you handle size/alignment dynamically then do you even need the "behind a pointer" restriction in the signature? I would think that "statically require every type-instantiation to have the same representation" ("behind a pointer") so that you don't need anything extra at runtime (i.e. analogous to the uniform boxed representation of most extant GCed languages), and "actually deal with all the statically-unknown representations at runtime instead" (so-called "intensional type analysis"), are two disjoint approaches.

What about higher kinded lifetimes?

Lifetime parameters are always erased, so the problems with runtime representation simply vanish. And the problem can be much harder to work around, often requiring unnecessary copying.

RFC #1598 is merged

This is an incremental step toward a more general feature commonly called "higher-kinded types," which is often ranked highly as a requested feature by Rust users.

Has there been any recent movement around this issue?

The foundational work in the compiler is being done to enable this. So not directly, but in a broad sense, yes.

@steveklabnik any links to what work is ahead before HKTs would be directly possible? I tried searching around in the rust issues and rfcs but couldn't figure out what the status is.

@steveklabnik any links to what work is ahead before HKTs would be directly possible? I tried searching around in the rust issues and rfcs but couldn't figure out what the status is.

The answer depends on what you mean by "directly." Work is underway to implement generic associated types (GATs), described in RFC #1598 and tracked in this tracking issue: rust-lang/rust#44265 Generic associated types are capable of representing the same type relationships as the higher kinded type class polymorphism in Haskell (for example) - what is normally called "HKT". But its not exactly the same feature, its just equally expressive.

There is no work underway on introducing a mechanism for polymorphism over higher kinded terms other than through generic associated types, and I don't currently see much impetus to add a separate faculty other than GATs.

Niko Matsakis wrote a blog series exploring the relationship between GATs and HKT, the last post contains links to all the previous ones: http://smallcultfollowing.com/babysteps/blog/2016/11/09/associated-type-constructors-part-4-unifying-atc-and-hkt/ (he calls GATs "ATCs" in that series)

EDIT: Looking at Niko's blog post again, I strongly recommend it. Its an excellent representation of our thinking about HKT in Rust right now.

@skeet70 i was typing a response but @withoutboats just beat me to it; GATs are not exactly this but can provide similar things.

"impl Trait in Traits" is also, in my understanding, equivalent to HKT.

It is true that we have no immediate plans to offer HKT directly at the time.

I'd love to see something like Archery baked into the language. It would allow people to easily and ergonomically abstract over Rc and Arc types.

The foundational work in the compiler is being done to enable this. So not directly, but in a broad sense, yes

Is there no update or proposal regarding this after two years?

Correct, those underlying features are still being worked on, and have not stabilized yet. Things take time.

Emulating HKTs with GATs on nightly gives us a nice looking (incorrect due to not constraining the return type to Self, but still shows that it may be possible) Monad:

#![feature(generic_associated_types)]

trait Monad {
    type Start;
    type End<B>: Monad;
    
    fn bind<B>(self, f: impl Fn(Self::Start) -> Self::End<B>) -> Self::End<B>;
}

It can be implemented for Option<T> like so:

impl<A> Monad for Option<A> {
    type Start = A;
    type End<B> = Option<B>;
    
    fn bind<B>(self, f: impl Fn(A) -> Option<B>) -> Option<B> {
        self.and_then(f)
    }
}


fn main() {
    let monad = Some(1).bind(|x| Some(x * 2));
    println!("{:?}", monad); // Some(2)
}

Yes, but that definition of monad is not precise enough. You can implement a monad where bind() accepts a function that return another type of monad, instead of "Self".