Revise associated types and interface parameters in Generics terminology (#1972)

Previous text was not clear, see https://discord.com/channels/655572317891461132/941071822756143115/1004607504396861540 .

Co-authored-by: Thomas Heller <thom.heller@gmail.com>
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
This commit is contained in:
josh11b
2022-08-15 15:00:00 -07:00
committed by GitHub
co-authored by Thomas Heller Jon Ross-Perkins
parent e6651836df
commit 90babcddc1
+47 -52
View File
@@ -637,59 +637,54 @@ conditions on the type argument. For example: `Array(T)` might implement
## Interface type parameters and associated types
Imagine an interface defining a container. Different containers will contain
different types of values, and the container API will have to refer to that
"element type" when defining the signature of methods like "insert" or "find".
If that element type is a parameter (input) to the interface type, we say it is
an _interface type parameter_; if it is an output, we say it is an _associated
type_. An associated type is a kind of [associated entity](#associated-entity).
_Interface type parameters_ and _associated types_ are both ways of allowing the
types in function signatures in an interface to vary. For example, different
[stacks](<https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>) will have
different element types. That element type would be used as the parameter type
of the `Push` function and the return type of the `Pop` function. As
[in Rust](https://rust-lang.github.io/rfcs/0195-associated-items.html#clearer-trait-matching),
we can distinguish these by whether they are input parameters or output
parameters:
Interface type parameter example:
- An interface type parameter is a parameter or input to the interface type.
That means they must be specified before an implementation of the interface
may be determined.
- In contrast, associated types are outputs. This means that they are
determined by the implementation, and need not be specified in a type
constraint.
Functions using an interface as a constraint need not specify the value of its
associated types. An associated type is a kind of
[associated entity](#associated-entity).
```
interface StackTP(ElementType:! Type)
fn Push[addr me: Self*](value: ElementType);
fn Pop[addr me: Self*]() -> ElementType;
}
```
Associated type example:
```
interface StackAT {
// Stack using associated types
interface Stack {
let ElementType:! Type;
fn Push[addr me: Self*](value: ElementType);
fn Pop[addr me: Self*]() -> ElementType;
}
// Works on any type implementing `Stack`. Return type
// is determined by the type's implementation of `Stack`.
fn PeekAtTopOfStack[T: Stack](s: T*) -> T.ElementType {
let ret: T.ElementType = s->Pop();
s->Push(ret);
return ret;
}
class Fruit;
class FruitStack {
// Implement `Stack` for `FruitStack`
// with `ElementType` set to `Fruit`.
impl as Stack where .ElementType == Fruit { ... }
}
```
Associated types are particularly called for when the implementation controls
the type, not the caller. For example, the iterator type for a container is
specific to the container and not something you would expect a user of the
interface to specify.
```
interface Iterator { ... }
interface Container {
// This does not make sense as an parameter to the container interface,
// since this type is determined from the container type.
let IteratorType:! Iterator;
...
fn Insert[addr me: Self*](position: IteratorType, value: ElementType);
}
class ListIterator(ElementType:! Type) {
...
impl as Iterator;
}
class List(ElementType:! Type) {
// Iterator type is determined by the container type.
impl as Container where .IteratorType = ListIterator(ElementType) {
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
...
}
}
}
```
Associated types are particularly called for when the implementation of the
interface determines the type, not the caller. For example, the iterator type
for a container is specific to the container and not something you would expect
a user of the interface to specify.
If you have an interface with type parameters, a type can have multiple impls
for different combinations of type parameters. As a result, type parameters may
@@ -701,7 +696,7 @@ For example, we might have an interface that says how to perform addition with
another type:
```
interface Addable(T:! Type) {
interface AddWith(T:! Type) {
let ResultType:! Type;
fn Add[me: Self](rhs: T) -> ResultType;
}
@@ -710,23 +705,23 @@ interface Addable(T:! Type) {
An `i32` value might support addition with `i32`, `u16`, and `f64` values.
```
impl i32 as Addable(i32) where .ResultType = i32 { ... }
impl i32 as Addable(u16) where .ResultType = i32 { ... }
impl i32 as Addable(f64) where .ResultType = f64 { ... }
impl i32 as AddWith(i32) where .ResultType = i32 { ... }
impl i32 as AddWith(u16) where .ResultType = i32 { ... }
impl i32 as AddWith(f64) where .ResultType = f64 { ... }
```
To write a generic function requiring a parameter to be `Addable`, there needs
To write a generic function requiring a parameter to be `AddWith`, there needs
to be some way to determine the type to add to:
```
// ✅ This is allowed, since the value of `T` is determined by the
// `y` parameter.
fn DoAdd[T:! Type, U:! Addable(T)](x: U, y: T) -> U.ResultType {
fn DoAdd[T:! Type, U:! AddWith(T)](x: U, y: T) -> U.ResultType {
return x.Add(y);
}
// ❌ This is forbidden, can't uniquely determine `T`.
fn CompileError[T:! Type, U:! Addable(T)](x: U) -> T;
fn CompileError[T:! Type, U:! AddWith(T)](x: U) -> T;
```
Once the interface parameter can be determined, that determines the values for
@@ -734,7 +729,7 @@ associated types, such as `ResultType` in the example. As always, calls with
types for which no implementation exists will be rejected at the call site:
```
// ❌ This is forbidden, no implementation of `Addable(Orange)`
// ❌ This is forbidden, no implementation of `AddWith(Orange)`
// for `Apple`.
DoAdd(apple, orange);
```