Skip to content

Commit 90babcd

Browse files
josh11bsithhelljonmeow
authored
Revise associated types and interface parameters in Generics terminology (carbon-language#1972)
Previous text was not clear, see https://discord.com/channels/655572317891461132/941071822756143115/1004607504396861540 . Co-authored-by: Thomas Heller <[email protected]> Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent e665183 commit 90babcd

File tree

1 file changed

+44
-49
lines changed

1 file changed

+44
-49
lines changed

docs/design/generics/terminology.md

+44-49
Original file line numberDiff line numberDiff line change
@@ -637,60 +637,55 @@ conditions on the type argument. For example: `Array(T)` might implement
637637

638638
## Interface type parameters and associated types
639639

640-
Imagine an interface defining a container. Different containers will contain
641-
different types of values, and the container API will have to refer to that
642-
"element type" when defining the signature of methods like "insert" or "find".
643-
If that element type is a parameter (input) to the interface type, we say it is
644-
an _interface type parameter_; if it is an output, we say it is an _associated
645-
type_. An associated type is a kind of [associated entity](#associated-entity).
646-
647-
Interface type parameter example:
640+
_Interface type parameters_ and _associated types_ are both ways of allowing the
641+
types in function signatures in an interface to vary. For example, different
642+
[stacks](<https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>) will have
643+
different element types. That element type would be used as the parameter type
644+
of the `Push` function and the return type of the `Pop` function. As
645+
[in Rust](https://rust-lang.github.io/rfcs/0195-associated-items.html#clearer-trait-matching),
646+
we can distinguish these by whether they are input parameters or output
647+
parameters:
648648

649-
```
650-
interface StackTP(ElementType:! Type)
651-
fn Push[addr me: Self*](value: ElementType);
652-
fn Pop[addr me: Self*]() -> ElementType;
653-
}
654-
```
649+
- An interface type parameter is a parameter or input to the interface type.
650+
That means they must be specified before an implementation of the interface
651+
may be determined.
652+
- In contrast, associated types are outputs. This means that they are
653+
determined by the implementation, and need not be specified in a type
654+
constraint.
655655

656-
Associated type example:
656+
Functions using an interface as a constraint need not specify the value of its
657+
associated types. An associated type is a kind of
658+
[associated entity](#associated-entity).
657659

658660
```
659-
interface StackAT {
661+
// Stack using associated types
662+
interface Stack {
660663
let ElementType:! Type;
661664
fn Push[addr me: Self*](value: ElementType);
662665
fn Pop[addr me: Self*]() -> ElementType;
663666
}
664-
```
665-
666-
Associated types are particularly called for when the implementation controls
667-
the type, not the caller. For example, the iterator type for a container is
668-
specific to the container and not something you would expect a user of the
669-
interface to specify.
670667
671-
```
672-
interface Iterator { ... }
673-
interface Container {
674-
// This does not make sense as an parameter to the container interface,
675-
// since this type is determined from the container type.
676-
let IteratorType:! Iterator;
677-
...
678-
fn Insert[addr me: Self*](position: IteratorType, value: ElementType);
668+
// Works on any type implementing `Stack`. Return type
669+
// is determined by the type's implementation of `Stack`.
670+
fn PeekAtTopOfStack[T: Stack](s: T*) -> T.ElementType {
671+
let ret: T.ElementType = s->Pop();
672+
s->Push(ret);
673+
return ret;
679674
}
680-
class ListIterator(ElementType:! Type) {
681-
...
682-
impl as Iterator;
683-
}
684-
class List(ElementType:! Type) {
685-
// Iterator type is determined by the container type.
686-
impl as Container where .IteratorType = ListIterator(ElementType) {
687-
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
688-
...
689-
}
690-
}
675+
676+
class Fruit;
677+
class FruitStack {
678+
// Implement `Stack` for `FruitStack`
679+
// with `ElementType` set to `Fruit`.
680+
impl as Stack where .ElementType == Fruit { ... }
691681
}
692682
```
693683

684+
Associated types are particularly called for when the implementation of the
685+
interface determines the type, not the caller. For example, the iterator type
686+
for a container is specific to the container and not something you would expect
687+
a user of the interface to specify.
688+
694689
If you have an interface with type parameters, a type can have multiple impls
695690
for different combinations of type parameters. As a result, type parameters may
696691
not be deduced in a function call. However, if the interface parameters are
@@ -701,7 +696,7 @@ For example, we might have an interface that says how to perform addition with
701696
another type:
702697

703698
```
704-
interface Addable(T:! Type) {
699+
interface AddWith(T:! Type) {
705700
let ResultType:! Type;
706701
fn Add[me: Self](rhs: T) -> ResultType;
707702
}
@@ -710,31 +705,31 @@ interface Addable(T:! Type) {
710705
An `i32` value might support addition with `i32`, `u16`, and `f64` values.
711706

712707
```
713-
impl i32 as Addable(i32) where .ResultType = i32 { ... }
714-
impl i32 as Addable(u16) where .ResultType = i32 { ... }
715-
impl i32 as Addable(f64) where .ResultType = f64 { ... }
708+
impl i32 as AddWith(i32) where .ResultType = i32 { ... }
709+
impl i32 as AddWith(u16) where .ResultType = i32 { ... }
710+
impl i32 as AddWith(f64) where .ResultType = f64 { ... }
716711
```
717712

718-
To write a generic function requiring a parameter to be `Addable`, there needs
713+
To write a generic function requiring a parameter to be `AddWith`, there needs
719714
to be some way to determine the type to add to:
720715

721716
```
722717
// ✅ This is allowed, since the value of `T` is determined by the
723718
// `y` parameter.
724-
fn DoAdd[T:! Type, U:! Addable(T)](x: U, y: T) -> U.ResultType {
719+
fn DoAdd[T:! Type, U:! AddWith(T)](x: U, y: T) -> U.ResultType {
725720
return x.Add(y);
726721
}
727722
728723
// ❌ This is forbidden, can't uniquely determine `T`.
729-
fn CompileError[T:! Type, U:! Addable(T)](x: U) -> T;
724+
fn CompileError[T:! Type, U:! AddWith(T)](x: U) -> T;
730725
```
731726

732727
Once the interface parameter can be determined, that determines the values for
733728
associated types, such as `ResultType` in the example. As always, calls with
734729
types for which no implementation exists will be rejected at the call site:
735730

736731
```
737-
// ❌ This is forbidden, no implementation of `Addable(Orange)`
732+
// ❌ This is forbidden, no implementation of `AddWith(Orange)`
738733
// for `Apple`.
739734
DoAdd(apple, orange);
740735
```

0 commit comments

Comments
 (0)