Skip to content

Commit 406400c

Browse files
committed
Associated type operators (a form of higher-kinded polymorphism).
1 parent bf9fca6 commit 406400c

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

0000-friends_in_high_kindednesses.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
- Feature Name: associated_type_operators
2+
- Start Date: 2016-04-29
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Allow type operators to be associated with traits. This is an incremental step
10+
toward a more general feature commonly called "higher-kinded types," which is
11+
often ranked highly as a requested feature by Rust users. This specific feature
12+
(associated type operators) resolves one of the most common use cases for
13+
higher-kindedness, is a relatively simple extension to the type system compared
14+
to other forms of higher-kinded polymorphism, and is forward compatible with
15+
more complex forms of higher-kinded polymorphism that may be introduced in the
16+
future.
17+
18+
19+
# Motivation
20+
[motivation]: #motivation
21+
22+
Consider the following trait as a representative motivating example:
23+
24+
```rust
25+
trait StreamingIterator {
26+
type Item<'a>;
27+
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
28+
}
29+
```
30+
31+
This trait is very useful - it allows for a kind of Iterator which yields
32+
values which have a lifetime tied to the lifetime of the reference passed to
33+
`next`. A particular obvious use case for this trait would be an iterator over
34+
a vector which yields overlapping, mutable subslices with each iteration. Using
35+
the standard `Iterator` interface, such an implementation would be invalid,
36+
because each slice would be required to exist for as long as the iterator,
37+
rather than for as long as the borrow initiated by `next`.
38+
39+
This trait cannot be expressed in Rust as it exists today, because it depends
40+
on a sort of higher-kinded polymorphism. This RFC would extend Rust to include
41+
that specific form of higher-kinded polymorphism, which is refered to here as
42+
associated type operators. This feature has a number of applications, but the
43+
primary application is along the same lines as the `StreamingIterator` trait:
44+
defining traits which yield types which have a lifetime tied to the local
45+
borrowing of the receiver type.
46+
47+
# Detailed design
48+
[design]: #detailed-design
49+
50+
## Background: What is kindedness?
51+
52+
"Higher-kinded types" is a vague term, conflating multiple language features
53+
under a single inaccurate banner. Let us discuss specifically the notion of a
54+
'kind' as background for this RFC. Kinds are often called 'the type of a type',
55+
the exact sort of unhelpful description that only makes sense to someone who
56+
already understands what is being explained. We'll take a different approach.
57+
58+
In a well-typed language, every expression has a type. Many expressions have
59+
what are sometimes called 'base types,' types which are primitive to the
60+
language and which cannot be described in terms of other types. In Rust, the
61+
types `bool`, `i64`, `usize`, and `char` are all prominent examples of base
62+
types. In contrast, there are other types which are formed by arranging other
63+
types - functions are a good example of this. Consider this simple function:
64+
65+
```rust
66+
fn not(x: bool) -> bool {
67+
!x
68+
}
69+
```
70+
71+
`not has the type `bool -> bool` (my apologies for using a syntax different
72+
from Rust's). Note that this is different from the type of `not(true)`, which
73+
is `bool`. This difference is important, by way of analogy, to understanding
74+
higher-kindedness.
75+
76+
In the analysis of kinds, all of these types - `bool`, `char`, `bool -> bool`
77+
and so on - have the kind `type`, which is often written `*`. This is a base
78+
kind, just as `bool` is a base type. In contrast, there are more complex kinds,
79+
such as `* -> *`. An example of an term of this kind is `Vec`, which takes a
80+
type as a parameter and evalues to a type. The difference between the kind of
81+
`Vec` and the kind of `Vec<i32>` (which is `*`) is analogous to the difference
82+
between the type of `not` and `not(true)`. Note that `Vec<T>` has the kind `*`,
83+
just like `Vec<i32>`: even though `T` is a type parameter, `Vec` is still being
84+
applied to a type, just like `not(x)` still has the type `bool` even if `x` is
85+
dynamically determined.
86+
87+
A relatively uncommon feature of Rust is that it has _two_ base kinds, whereas
88+
many languages which deal with higher-kindedness only have the base kind `*`.
89+
The other base kind of Rust is the lifetime parameter, which for conveniences
90+
sake we will represent as `&`. For a type `Foo<'a>`, the kind of `Foo` is
91+
`& -> *`.
92+
93+
Terms of a higher kind are often called 'type operators'; type operators which
94+
evaluate to a type are called 'type constructors.' The concept of
95+
'higher-kinded types' usually refers to the ability to write code which is
96+
polymorphic over type operators in some way, such as implementing a trait for a
97+
type operator. This proposal is to allow a type operator to be associated with
98+
a trait, in the same way that a type or a const can be associated with a trait
99+
today.
100+
101+
## The basic requirements of associated type operators
102+
103+
Adding associated type operators to the language requires the introduction of
104+
four discrete constructs:
105+
106+
1. In a definition of a trait, an associated type operator can be declared.
107+
2. In the position of any type within the definition of that trait, the
108+
associated type operator can be applied to a type parameter or a concrete
109+
type which is in scope.
110+
3. In the implementation of that trait, a type operator of the correct kind can
111+
be assigned to the declared associated type operator.
112+
4. When bounding a type parameter by that trait, that trait can be bound to
113+
have a concrete type operator as this associated type operator.
114+
115+
## Partial application
116+
117+
In order for this feature to be useful, we will have to allow for type
118+
operators to partially applied. Many languages with higher-kinded polymorphism
119+
use currying as an alternative to partial application. Rust does not have
120+
currying at the level of expressions, and currying would not be sufficient
121+
to enable the use cases that exist for type operators, so this RFC does not
122+
propose using currying for higher-kinded polymorphism.
123+
124+
As an example, the reference operator has the kind `&, * -> *`, taking both
125+
a lifetime and a type to produce a new type. With currying, the two parameters
126+
to the reference operator would have to have a defined order, and it would
127+
be possible to partially apply only one of the parameters to the reference
128+
operator. That is, if it were `& -> * -> *`, one could apply it to a lifetime
129+
to produce a `* -> *` operator, but one could not apply it to a type to produce
130+
a `& -> *` operator. If it were defined as `* -> & -> *`, it would be
131+
restricted in the opposite way. Because Rust makes use of two base kinds,
132+
currying would severely restrict the forms of abstraction enabled by Rust.
133+
134+
Instead, when defining an associated type operator, an anonymous type operator
135+
can be constructed from a type operator with more parameters by applying any
136+
of the parameters to that operator. The syntax discussed below makes it
137+
unambiguous and easy to see which parameters remain undetermined at the point
138+
of assigning the associated type operator to a concrete type operator.
139+
140+
When used in a type position, of course, all of the parameters to an associated
141+
type operator must have been applied to concrete types or type parameters that
142+
are in scope.
143+
144+
## Associated type operators in bounds
145+
146+
This RFC proposes making associated type operators available in bounds only
147+
as concrete type operators. Because higher-kinded traits cannot be defined, and
148+
traits cannot be implemented for type operators, it is not possible to bound
149+
associated type operators by traits.
150+
151+
Even without higher-kinded traits, it could be useful to bound associated type
152+
operators with some sort of higher-rank syntax, as in:
153+
154+
```rust
155+
T where T: StreamingIterator, for<'a> T::Item<'a>: Display
156+
```
157+
158+
However, this RFC does not propose adding this feature.
159+
160+
## Benefits of implementing only this feature before other higher-kinded polymorphisms
161+
162+
This feature is the first 20% of higher-kinded polymorphism which is worth 50%
163+
of the full implementation. It is the ideal starting point, as it will enable
164+
many constructs while adding relatively few complicates to the type system. By
165+
implementing only associated type operators, we sidestep several issues:
166+
167+
* Defining higher-kinded traits
168+
* Implementing traits for type operators
169+
* Higher order type operators
170+
* Type operator parameters bound by higher-kinded traits
171+
* Type operator parameters applied to a given type or type parameter
172+
173+
## Proposed syntax
174+
175+
The syntax proposed in this RFC is very similar to the syntax of associated
176+
types and type aliases. An advantage of this is that users less familiar with
177+
the intimate details of kindedness will hopefully find this feature intuitive.
178+
179+
To declare an associated type operator, simply declare an associated type
180+
with parameters on the type name, as in:
181+
182+
```rust
183+
trait StreamingIterator {
184+
type Item<'a>;
185+
...
186+
}
187+
```
188+
189+
Here `Item` is an associated type operator of the kind `& -> *`.
190+
191+
To apply the associated type operator, simply use it in the position where
192+
a normal type operator would be used instead, as in:
193+
194+
```rust
195+
trait StreamingIterator {
196+
...
197+
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
198+
}
199+
```
200+
201+
To assign the associated type operator, use the parameters in the type
202+
declaration on the right-hand side of the type expression, as in:
203+
204+
```rust
205+
impl<T> StreamingIterator for StreamIter<T> {
206+
type Item<'a> = &'a [T];
207+
...
208+
}
209+
```
210+
211+
Note here that a slice reference has the kind `&, * -> *`, but the local type
212+
parameter `T` is applied to it through partial application to form a type
213+
operator `& -> *`. The syntax makes it clear that the unapplied parameter is
214+
the lifetime `'a`, because `'a` is introduced on the type Item.
215+
216+
This has the same appearance as the declaration of type operator aliases which
217+
are not associated with the trait.
218+
219+
To add a concrete bound as an associated type operator, the syntax is the same
220+
as adding a concrete bound of an associated type. Here, any types or lifetimes
221+
which are parameters to the associated type operator are omitted (not elided):
222+
223+
```rust
224+
where T: StreamingIterator<Item=&[u8]>
225+
```
226+
227+
`&[u8]` is not an elided form of some `&'a [u8]`, but a type operator of the
228+
kind `& -> *`.
229+
230+
231+
However, life time parameters can be elided when applied to associated type
232+
operators in the type position just as they can be elided for concrete type
233+
operators, as in this case, providing a full definition of `StreamingIterator`:
234+
235+
```rust
236+
trait StreamingIterator {
237+
type Item<'a>;
238+
fn next(&mut self) -> Option<Self::Item>;
239+
}
240+
```
241+
242+
243+
# Drawbacks
244+
[drawbacks]: #drawbacks
245+
246+
## Drawbacks to the concept
247+
248+
This adds complexity to the language, and implements a part of higher-kinded
249+
polymorphism without all of the benefits that come along with it. There are
250+
valid arguments in favor of waiting until additional forms of higher-kinded
251+
polymorphism have been worked out, as well as in favor of never implementing
252+
higher-kinded polymorphism at all.
253+
254+
## Drawbacks to the syntax
255+
256+
Though this syntax is a natural fit for associated type operators, it is not
257+
a natural syntax for other forms of higher-kinded polymorphism. As a result,
258+
the syntaxes of two related forms of polymorphism will be significantly
259+
different. We believe this cost is justified by the advantages of making the
260+
syntax similar to associated types.
261+
262+
# Alternatives
263+
[alternatives]: #alternatives
264+
265+
An alternative is to push harder on higher-ranked lifetimes, possibly
266+
introducing some elision that would make them easier to use.
267+
268+
Currently, an approximation of `StreamingIterator` can be defined like this:
269+
270+
```rust
271+
trait StreamingIterator<'a> {
272+
type Item: 'a;
273+
fn next(&'a self) -> Option<Self::Item>;
274+
}
275+
```
276+
277+
You can then bound types as `T: for<'a> StreamingIterator<'a>` to avoid the
278+
lifetime parameter infecting everything `StreamingIterator` appears.
279+
280+
However, this only partially prevents the infectiveness of `StreamingIterator`,
281+
only allows for some of the types that associated type operators can express,
282+
and is in generally a hacky attempt to work around the limitation rather than
283+
an equivalent alternative.
284+
285+
# Unresolved questions
286+
[unresolved]: #unresolved-questions
287+
288+
This design does not resolve the question of introducing more advanced forms of
289+
higher-kinded polymorphism. This document does not describe the details of
290+
implementing this RFC in terms of rustc's current typeck, because the author
291+
is not familiar with that code. This document is certainly inadequate in its
292+
description of this feature, most likely in relation to partial application,
293+
because of the author's ignorance and personal defects.

0 commit comments

Comments
 (0)