From 48d539776436e0832d47a70d7ebc21365a351e9b Mon Sep 17 00:00:00 2001 From: josh11b Date: Sun, 5 Sep 2021 15:50:53 -0700 Subject: [PATCH] Generics details 2: adapters, associated types, parameterized interfaces (#731) This proposal goes into the details for these features of generics: - adapters: for creating new types compatible with existing types but with different interface implementations - associated types: allowing an interface implementation to specify some types to use in method signatures - interface parameters: creating a family of interfaces, where types can implement more than one This is a continuation of #553 . It has been summarized in these presentations: - adapters: [1](https://docs.google.com/presentation/d/1bg6q0Q9Sk4YpRbNA3D3H34xYtaEO8ScAUNUZK2UTi80/edit?resourcekey=0-6-Y6e1mfRUmHg-Zk65Gc5A#slide=id.gcf40df1c7b_0_37) and [2](https://docs.google.com/presentation/d/17KG0TeJ4OChMRdLJPS8TE_K6SoL4lFy1FUGr2CDzX-A/edit?resourcekey=0-kLnZqd5NrbGSwmbunTyB-A#slide=id.g7a37009490_0_0) - [associated types and interface parameters](https://docs.google.com/presentation/d/19hPpUjxQ0H1lUSLy5QjS2910Cpc7UdNKpF580fFsCGw/edit?resourcekey=0-ky9XGRC1I8X0Ffw6eqh7WQ#slide=id.p) Co-authored-by: Wolff Dobson Co-authored-by: Richard Smith --- docs/design/generics/details.md | 784 ++++++++++++++++++++++++++-- docs/design/generics/overview.md | 117 ++++- docs/design/generics/terminology.md | 131 +++-- proposals/README.md | 1 + proposals/p0731.md | 384 ++++++++++++++ 5 files changed, 1331 insertions(+), 86 deletions(-) create mode 100644 proposals/p0731.md diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index e62623168a918..3444fd63faad4 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -30,12 +30,20 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Diamond dependency issue](#diamond-dependency-issue) - [Use case: overload resolution](#use-case-overload-resolution) - [Type compatibility](#type-compatibility) +- [Adapting types](#adapting-types) + - [Adapter compatibility](#adapter-compatibility) + - [Extending adapter](#extending-adapter) + - [Use case: Using independent libraries together](#use-case-using-independent-libraries-together) + - [Adapter with stricter invariants](#adapter-with-stricter-invariants) + - [Application: Defining an impl for use by other types](#application-defining-an-impl-for-use-by-other-types) +- [Associated constants](#associated-constants) + - [Associated class functions](#associated-class-functions) +- [Associated types](#associated-types) + - [Model](#model-1) +- [Parameterized interfaces](#parameterized-interfaces) + - [Impl lookup](#impl-lookup) + - [Parameterized structural interfaces](#parameterized-structural-interfaces) - [Future work](#future-work) - - [Adapting types](#adapting-types) - - [Associated constants](#associated-constants) - - [Associated types](#associated-types) - - [Parameterized interfaces](#parameterized-interfaces) - - [Impl lookup](#impl-lookup) - [Constraints](#constraints) - [Conditional conformance](#conditional-conformance) - [Parameterized impls](#parameterized-impls) @@ -183,13 +191,11 @@ interface Vector { } ``` -The syntax here is to match how the same members would be defined in a type. -Each declaration in the interface defines an _associated item_ (same -[terminology as Rust](https://doc.rust-lang.org/reference/items/associated-items.html)). -In this example, `Vector` has two associated methods, `Add` and `Scale`. - -**References:** Method syntax for types was decided in -[question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494). +The syntax here is to match +[how the same members would be defined in a type](/docs/design/classes.md#methods). +Each declaration in the interface defines an +[associated entity](terminology.md#associated-entity). In this example, `Vector` +has two associated methods, `Add` and `Scale`. An interface defines a type-of-type, that is a type whose values are types. The values of an interface are specifically @@ -203,7 +209,7 @@ interface. Carbon interfaces are ["nominal"](terminology.md#nominal-interfaces), which means that types explicitly describe how they implement interfaces. An ["impl"](terminology.md#impls-implementations-of-interfaces) defines how one -interface is implemented for a type. Every associated item is given a +interface is implemented for a type. Every associated entity is given a definition. Different types satisfying `Vector` can have different definitions for `Add` and `Scale`, so we say their definitions are _associated_ with what type is implementing `Vector`. The `impl` defines what is associated with the @@ -327,7 +333,7 @@ class GameBoard { fn Draw[me: Self]() { ... } } impl as EndOfGame { - // Error: `GameBoard` has two methods named + // ❌ Error: `GameBoard` has two methods named // `Draw` with the same signature. fn Draw[me: Self]() { ... } fn Winner[me: Self](player: Int) { ... } @@ -405,7 +411,7 @@ visible: ``` var a: Point2 = (.x = 1.0, .y = 2.0); // `a` does *not* have `Add` and `Scale` methods: -// Error: a.Add(a.Scale(2.0)); +// ❌ Error: a.Add(a.Scale(2.0)); // Cast from Point2 implicitly var b: Point2 as Vector = a; @@ -567,7 +573,7 @@ However, for another type implementing `Vector` but out-of-line using an ``` fn AddAndScaleForPoint2(a: Point2, b: Point2, s: Double) -> Point2 { - // ERROR: `Point2` doesn't have `Add` or `Scale` methods. + // ❌ ERROR: `Point2` doesn't have `Add` or `Scale` methods. return a.Add(b).Scale(s); } ``` @@ -1144,7 +1150,7 @@ interface ConvertibleTo(T:! Type) { ... } // A type can only implement `PreferredConversion` once. interface PreferredConversion { - let AssociatedType: Type; + let AssociatedType:! Type; extends ConvertibleTo(AssociatedType); } ``` @@ -1422,38 +1428,742 @@ var m: HashMap(String, Int); PrintValue(m, "key"); ``` -## Future work - -### Adapting types +## Adapting types Since interfaces may only be implemented for a type once, and we limit where implementations may be added to a type, there is a need to allow the user to -switch the type of a value to access different interface implementations. See -["adapting a type" in the terminology document](terminology.md#adapting-a-type). +switch the type of a value to access different interface implementations. We +therefore provide a way to create new types +[compatible with](terminology.md#compatible-types) existing types with different +APIs, in particular with different interface implementations, by +[adapting](terminology.md#adapting-a-type) them: + +``` +interface Printable { + fn Print[me: Self](); +} +interface Comparable { + fn Less[me: Self](that: Self) -> Bool; +} +class Song { + impl as Printable { fn Print[me: Self]() { ... } } +} +adapter SongByTitle for Song { + impl as Comparable { + fn Less[me: Self](that: Self) -> Bool { ... } + } +} +adapter FormattedSong for Song { + impl as Printable { fn Print[me: Self]() { ... } } +} +adapter FormattedSongByTitle for Song { + impl as Printable = FormattedSong as Printable; + impl as Comparable = SongByTitle as Comparable; +} +``` + +This allows us to provide implementations of new interfaces (as in +`SongByTitle`), provide different implementations of the same interface (as in +`FormattedSong`), or mix and match implementations from other compatible types +(as in `FormattedSongByTitle`). The rules are: + +- You can add any declaration that you could add to a class except for + declarations that would change the representation of the type. This means + you can add functions, interface implementations, and aliases, but not + fields, base classes, or virtual functions. +- The adapted type is compatible with the original type, and that relationship + is an equivalence class, so all of `Song`, `SongByTitle`, `FormattedSong`, + and `FormattedSongByTitle` end up compatible with each other. +- Since adapted types are compatible with the original type, you may + explicitly cast between them, but there is no implicit casting between these + types (unlike between a type and one of its facet types / impls). +- For the purposes of generics, we only need to support adding interface + implementations. But this `adapter` feature could be used more generally, + such as to add methods. + +Inside an adapter, the `Self` type matches the adapter. Members of the original +type may be accessed like any other facet type; either by a cast: + +``` +adapter SongByTitle for Song { + impl as Comparable { + fn Less[me: Self](that: Self) -> Bool { + return (this as Song).Title() < (that as Song).Title(); + } + } +} +``` + +or using qualified names: + +``` +adapter SongByTitle for Song { + impl as Comparable { + fn Less[me: Self](that: Self) -> Bool { + return this.(Song.Title)() < that(Song.Title)(); + } + } +} +``` + +**Open question:** As an alternative to: + +``` +impl as Printable = FormattedSong as Printable; +``` + +we could allow users to write: + +``` +impl as Printable = FormattedSong; +``` + +This would remove ceremony that the compiler doesn't need. The concern is +whether it makes sense or is a category error. In this example, is +`FormattedSong`, a type, a suitable value to provide when asking for a +`Printable` implementation? An argument for this terser syntax is that the +implicit conversion is legal in other contexts: + +``` +// ✅ Legal implicit conversion +var v:! Printable = FormattedSong; +``` + +**Comparison with other languages:** This matches the Rust idiom called +"newtype", which is used to implement traits on types while avoiding coherence +problems, see +[here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) +and +[here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial). +Rust's mechanism doesn't directly support reusing implementations, though some +of that is provided by macros defined in libraries. Haskell has a +[`newtype` feature](https://wiki.haskell.org/Newtype) as well. Haskell's feature +doesn't directly support reusing implementations either, but the most popular +compiler provides it as +[an extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html). + +### Adapter compatibility + +The framework from the [type compatibility section](#type-compatibility) allows +us to evaluate when we can cast between two different arguments to a +parameterized type. Consider three compatible types, all of which implement +`Hashable`: + +``` +class Song { + impl as Hashable { ... } + impl as Printable { ... } +} +adapter SongHashedByTitle for Song { + impl as Hashable { ... } +} +adapter PlayableSong for Song { + impl as Hashable = Song as Hashable; + impl as Media { ... } +} +``` + +Observe that `Song as Hashable` is different from +`SongHashedByTitle as Hashable`, since they have different definitions of the +`Hashable` interface even though they are compatible types. However +`Song as Hashable` and `PlayableSong as Hashable` are almost the same. In +addition to using the same data representation, they both implement one +interface, `Hashable`, and use the same implementation for that interface. The +one difference between them is that `Song as Hashable` may be implicitly cast to +`Song`, which implements interface `Printable`, and `PlayableSong as Hashable` +may be implicilty cast to `PlayableSong`, which implements interface `Media`. +This means that it is safe to cast between +`HashMap(Song, Int) == HashMap(Song as Hashable, Int)` and +`HashMap(PlayableSong, Int) == HashMap(PlayableSong as Hashable, Int)` (though +maybe only with an explicit cast) but +`HashMap(SongHashedByTitle, Int) == HashMap(SongHashByTitle as Hashable, Int)` +is incompatible. This is a relief, because we know that in practice the +invariants of a `HashMap` implementation rely on the hashing function staying +the same. + +### Extending adapter + +Frequently we expect that the adapter type will want to preserve most or all of +the API of the original type. The two most common cases expected are adding and +replacing an interface implementation. Users would indicate that an adapter +starts from the original type's existing API by using the `extends` keyword +instead of `for`: + +``` +class Song { + impl as Hashable { ... } + impl as Printable { ... } +} + +adapter SongByArtist extends Song { + // Add an implementation of a new interface + impl as Comparable { ... } + + // Replace an existing implementation of an interface + // with an alternative. + impl as Hashable { ... } +} +``` + +The resulting type `SongByArtist` would: + +- implement `Comparable`, unlike `Song`, +- implement `Hashable`, but differently than `Song`, and +- implement `Printable`, inherited from `Song`. + +Unlike the similar `class B extends A` notation, `adaptor B extends A` is +permitted even if `A` is a final class. Also, there is no implicit conversion +from `B` to `A`, matching `adapter`...`for` but unlike class extension. + +**Future work:** We may need additional mechanisms for changing the API in the +adapter. For example, to resolve conflicts we might want to be able to move the +implementation of a specific interface into an [external impl](#external-impl). + +### Use case: Using independent libraries together + +Imagine we have two packages that are developed independently. Package +`CompareLib` defines an interface `CompareLib.Comparable` and a generic +algorithm `CompareLib.Sort` that operates on types that implement +`CompareLib.Comparable`. Package `SongLib` defines a type `SongLib.Song`. +Neither has a dependency on the other, so neither package defines an +implementation for `CompareLib.Comparable` for type `SongLib.Song`. A user that +wants to pass a value of type `SongLib.Song` to `CompareLib.Sort` has to define +an adapter that provides an implementation of `CompareLib.Comparable` for +`SongLib.Song`. This adapter will probably use the +[`extends` facility of adapters](#extending-adapter) to preserve the +`SongLib.Song` API. + +``` +import CompareLib; +import SongLib; + +adapter Song extends SongLib.Song { + impl as CompareLib.Comparable { ... } +} +// Or, to keep the names from CompareLib.Comparable out of Song's API: +adapter Song extends SongLib.Song { } +external impl Song as CompareLib.Comparable { ... } +``` + +The caller can either cast `SongLib.Song` values to `Song` when calling +`CompareLib.Sort` or just start with `Song` values in the first place. + +``` +var lib_song: SongLib.Song = ...; +CompareLib.Sort((lib_song as Song,)); + +var song: Song = ...; +CompareLib.Sort((song,)); +``` + +### Adapter with stricter invariants + +**Future work:** Rust also uses the newtype idiom to create types with +additional invariants or other information encoded in the type +([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html), +[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction), +[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)). +This is used to record in the type system that some data has passed validation +checks, like `ValidDate` with the same data layout as `Date`. Or to record the +units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet` +versus `Meters`. We should have some way of restricting the casts between a type +and an adapter to address this use case. + +### Application: Defining an impl for use by other types + +Let's say we want to provide a possible implementation of an interface for use +by types for which that implementation would be appropriate. We can do that by +defining an adapter implementing the interface that is parameterized on the type +it is adapting. That impl may then be pulled in using the `impl as ... = ...;` +syntax. + +``` +interface Comparable { + fn Less[me: Self](that: Self) -> Bool; +} +adapter ComparableFromDifferenceFn + (T:! Type, Difference:! fnty(T, T)->Int) for T { + impl as Comparable { + fn Less[me: Self](that: Self) -> Bool { + return Difference(this, that) < 0; + } + } +} +class IntWrapper { + var x: Int; + fn Difference(this: Self, that: Self) { + return that.x - this.x; + } + impl as Comparable = + ComparableFromDifferenceFn(IntWrapper, Difference) + as Comparable; +} +``` + +## Associated constants + +In addition to associated methods, we allow other kinds of +[associated entities](terminology.md#associated-entity). For consistency, we use +the same syntax to describe a constant in an interface as in a type without +assigning a value. As constants, they are declared using the `let` introducer. +For example, a fixed-dimensional point type could have the dimension as an +associated constant. + +``` +interface NSpacePoint { + let N:! Int; + // The following require: 0 <= i < N. + fn Get[addr me: Self*](i: Int) -> Float64; + fn Set[addr me: Self*](i: Int, value: Float64); + // Associated constants may be used in signatures: + fn SetAll[addr me: Self*](value: Array(Float64, N)); +} +``` + +Implementations of `NSpacePoint` for different types might have different values +for `N`: + +``` +class Point2D { + impl as NSpacePoint { + let N:! Int = 2; + fn Get[addr me: Self*](i: Int) -> Float64 { ... } + fn Set[addr me: Self*](i: Int, value: Float64) { ... } + fn SetAll[addr me: Self*](value: Array(Float64, 2)) { ... } + } +} + +class Point3D { + impl as NSpacePoint { + let N:! Int = 3; + fn Get[addr me: Self*](i: Int) -> Float64 { ... } + fn Set[addr me: Self*](i: Int, value: Float64) { ... } + fn SetAll[addr me: Self*](value: Array(Float64, 3)) { ... } + } +} +``` + +And these values may be accessed as members of the type: + +``` +Assert(Point2D.N == 2); +Assert(Point3D.N == 3); + +fn PrintPoint[PointT:! NSpacePoint](p: PointT) { + for (var i: Int = 0; i < PointT.N; ++i) { + if (i > 0) { Print(", "); } + Print(p.Get(i)); + } +} + +fn ExtractPoint[PointT:! NSpacePoint]( + p: PointT, + dest: Array(Float64, PointT.N)*) { + for (var i: Int = 0; i < PointT.N; ++i) { + (*dest)[i] = p.Get(i); + } +} +``` + +**Comparison with other languages:** This feature is also called +[associated constants in Rust](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants). + +**Aside:** In general, the use of `:!` here means these `let` declarations will +only have compile-time and not runtime storage associated with them. + +### Associated class functions + +To be consistent with normal +[class function](/docs/design/classes.md#class-functions) declaration syntax, +associated class functions are written: + +``` +interface DeserializeFromString { + fn Deserialize(serialized: String) -> Self; +} + +class MySerializableType { + var i: Int; + + impl as DeserializeFromString { + fn Deserialize(serialized: String) -> Self { + return (.i = StringToInt(serialized)); + } + } +} + +var x: MySerializableType = MySerializableType.Deserialize("3"); + +fn Deserialize(T:! DeserializeFromString, serialized: String) -> T { + return T.Deserialize(serialized); +} +var y: MySerializableType = Deserialize(MySerializableType, "4"); +``` + +This is instead of declaring an associated constant using `let` with a function +type. + +Together associated methods and associated class functions are called +_associated functions_, much like together methods and class functions are +called [member functions](/docs/design/classes.md#member-functions). + +## Associated types + +Associated types are [associated entities](terminology.md#associated-entity) +that happen to be types. These are particularly interesting since they can be +used in the signatures of associated methods or functions, to allow the +signatures of methods to vary from implementation to implementation. We already +have one example of this: the `Self` type discussed +[above in the "Interfaces" section](#interfaces). For other cases, we can say +that the interface declares that each implementation will provide a type under a +specific name. For example: + +``` +interface StackAssociatedType { + let ElementType:! Type; + fn Push[addr me: Self*](value: ElementType); + fn Pop[addr me: Self*]() -> ElementType; + fn IsEmpty[addr me: Self*]() -> Bool; +} +``` + +Here we have an interface called `StackAssociatedType` which defines two +methods, `Push` and `Pop`. The signatures of those two methods declare them as +accepting or returning values with the type `ElementType`, which any implementer +of `StackAssociatedType` must also define. For example, maybe `DynamicArray` +implements `StackAssociatedType`: + +``` +class DynamicArray(T:! Type) { + class IteratorType { ... } + fn Begin[addr me: Self*]() -> IteratorType; + fn End[addr me: Self*]() -> IteratorType; + fn Insert[addr me: Self*](pos: IteratorType, value: T); + fn Remove[addr me: Self*](pos: IteratorType); + + impl as StackAssociatedType { + // Set the associated type `ElementType` to `T`. + let ElementType:! Type = T; + fn Push[addr me: Self*](value: ElementType) { + this->Insert(this->End(), value); + } + fn Pop[addr me: Self*]() -> ElementType { + var pos: IteratorType = this->End(); + Assert(pos != this->Begin()); + --pos; + returned var ret: ElementType = *pos; + this->Remove(pos); + return var; + } + fn IsEmpty[addr me: Self*]() -> Bool { + return this->Begin() == this->End(); + } + } +} +``` + +**Alternatives considered:** See +[other syntax options considered for specifying associated types](/proposals/p0731.md#syntax-for-associated-constants). +In particular, it was deemed that +[Swift's approach of inferring the associated type from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) +was unneeded complexity. -### Associated constants +The definition of the `StackAssociatedType` is sufficient for writing a generic +function that operates on anything implementing that interface, for example: -In addition to associated methods, we will allow other kinds of associated items -associating values with types implementing an interface. +``` +fn PeekAtTopOfStack[StackType:! StackAssociatedType](s: StackType*) + -> StackType.ElementType { + var top: StackType.ElementType = s->Pop(); + s->Push(top); + return top; +} + +var my_array: DynamicArray(i32) = (1, 2, 3); +// PeekAtTopOfStack's `StackType` is set to +// `DynamicArray(i32) as StackAssociatedType`. +// `StackType.ElementType` becomes `i32`. +Assert(PeekAtTopOfStack(my_array) == 3); +``` + +Associated types can also be implemented using a +[member type](/docs/design/classes.md#member-type). + +``` +interface Container { + let IteratorType:! Iterator; + ... +} -### Associated types +class DynamicArray(T:! Type) { + ... + impl as Container { + class IteratorType { ... } + ... + } +} +``` + +For context, see +["Interface type parameters and associated types" in the generics terminology document](terminology.md#interface-type-parameters-versus-associated-types). -Associated types are associated constants that happen to be types. These are -particularly interesting since they can be used in the signatures of associated -methods or functions, to allow the signatures of methods to vary from -implementation to implementation. +**Comparison with other languages:** Both +[Rust](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) +and [Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID189) +support associated types. -### Parameterized interfaces +### Model + +The associated type can be modeled by a witness table field in the interface's +witness table. + +``` +interface Iterator { + fn Advance[addr me: Self*](); +} + +interface Container { + let IteratorType:! Iterator; + fn Begin[addr me: Self*]() -> IteratorType; +} +``` + +is represented by: + +``` +class Iterator(Self:! Type) { + var Advance: fnty(this: Self*); + ... +} +class Container(Self:! Type) { + // Representation type for the iterator. + let IteratorType:! Type; + // Witness that IteratorType implements Iterator. + var iterator_impl: Iterator(IteratorType)*; + + // Method + var Begin: fnty (this: Self*) -> IteratorType; + ... +} +``` + +## Parameterized interfaces Associated types don't change the fact that a type can only implement an -interface at most once. If instead you want a family of related interfaces, each -of which could be implemented for a given type, you could use parameterized -interfaces instead. +interface at most once. + +If instead you want a family of related interfaces, one per possible value of a +type parameter, multiple of which could be implemented for a single type, you +would use parameterized interfaces. To write a parameterized version the stack +interface instead of using associated types, write a parameter list after the +name of the interface instead of the associated type declaration: + +``` +interface StackParameterized(ElementType:! Type) { + fn Push[addr me: Self*](value: ElementType); + fn Pop[addr me: Self*]() -> ElementType; + fn IsEmpty[addr me: Self*]() -> Bool; +} +``` + +Then `StackParameterized(Fruit)` and `StackParameterized(Veggie)` would be +considered different interfaces, with distinct implementations. + +``` +class Produce { + var fruit: DynamicArray(Fruit); + var veggie: DynamicArray(Veggie); + impl as StackParameterized(Fruit) { + fn Push[addr me: Self*](value: Fruit) { + this->fruit.Push(value); + } + fn Pop[addr me: Self*]() -> Fruit { + return this->fruit.Pop(); + } + fn IsEmpty[addr me: Self*]() -> Bool { + return this->fruit.IsEmpty(); + } + } + impl as StackParameterized(Veggie) { + fn Push[addr me: Self*](value: Veggie) { + this->veggie.Push(value); + } + fn Pop[addr me: Self*]() -> Veggie { + return this->veggie.Pop(); + } + fn IsEmpty[addr me: Self*]() -> Bool { + return this->veggie.IsEmpty(); + } + } +} +``` + +Unlike associated types in interfaces and parameters to types, interface +parameters can't be deduced. For example, if we were to rewrite +[the `PeekAtTopOfStack` example in the "associated types" section](#associated-types) +for `StackParameterized(T)` it would generate a compile error: + +``` +// ❌ Error: can't deduce interface parameter `T`. +fn BrokenPeekAtTopOfStackParameterized + [T:! Type, StackType:! StackParameterized(T)] + (s: StackType*) -> T { ... } +``` + +This error is because the compiler can not determine if `T` should be `Fruit` or +`Veggie` when passing in argument of type `Produce*`. The function's signature +would have to be changed so that the value for `T` could be determined from the +explicit parameters. + +``` +fn PeekAtTopOfStackParameterized + [T:! Type, StackType:! StackParameterized(T)] + (s: StackType*, _: singleton_type_of(T)) -> T { ... } + +var produce: Produce = ...; +var top_fruit: Fruit = + PeekAtTopOfStackParameterized(&produce, Fruit); +var top_veggie: Veggie = + PeekAtTopOfStackParameterized(&produce, Veggie); +``` + +The pattern `_: singleton_type_of(T)` is a placeholder syntax for an expression +that will only match `T`, until issue +[#578: Value patterns as function parameters](https://github.com/carbon-language/carbon-lang/issues/578) +is resolved. Using that pattern in the explicit parameter list allows us to make +`T` available earlier in the declaration so it can be passed as the argument to +the parameterized interface `StackParameterized`. + +This approach is useful for the `ComparableTo(T)` interface, where a type might +be comparable with multiple other types, and in fact interfaces for +[operator overloads](#operator-overloading) more generally. Example: + +``` +interface EquatableWith(T:! Type) { + fn Equals[me: Self](that: T) -> Bool; + ... +} +class Complex { + var real: f64; + var imag: f64; + // Can implement this interface more than once as long as it has different + // arguments. + impl as EquatableWith(Complex) { ... } + impl as EquatableWith(f64) { ... } +} +``` + +All interface parameters must be marked as "generic", using the `:!` syntax. +This reflects these two properties of these parameters: + +- They must be resolved at compile-time, and so can't be passed regular + dynamic values. +- We allow either generic or template values to be passed in. + +**Context:** See +[interface type parameters](terminology.md#interface-type-parameters-versus-associated-types) +in the terminology doc. + +**Note:** Interface parameters aren't required to be types, but that is the vast +majority of cases. As an example, if we had an interface that allowed a type to +define how the tuple-member-read operator would work, the index of the member +could be an interface parameter: + +``` +interface ReadTupleMember(index:! u32) { + let T:! Type; + // Returns me[index] + fn Get[me: Self]() -> T; +} +``` + +This requires that the index be known at compile time, but allows different +indices to be associated with different types. + +**Caveat:** When implementing an interface twice for a type, you need to be sure +that the interface parameters will always be different. For example: + +``` +interface Map(FromType:! Type, ToType:! Type) { + fn Map[addr me: Self*](needle: FromType) -> Optional(ToType); +} +class Bijection(FromType:! Type, ToType:! Type) { + impl as Map(FromType, ToType) { ... } + impl as Map(ToType, FromType) { ... } +} +// ❌ Error: Bijection has two impls of interface Map(String, String) +var oops: Bijection(String, String) = ...; +``` -#### Impl lookup +In this case, it would be better to have an [adapting type](#adapting-types) to +contain the `impl` for the reverse map lookup, instead of implementing the `Map` +interface twice: -We will have rules limiting where interface implementations are defined for -coherence. +``` +class Bijection(FromType:! Type, ToType:! Type) { + impl as Map(FromType, ToType) { ... } +} +adapter ReverseLookup(FromType:! Type, ToType:! Type) + for Bijection(FromType, ToType) { + impl as Map(ToType, FromType) { ... } +} +``` + +**Comparison with other languages:** Rust calls +[traits with type parameters "generic traits"](https://doc.rust-lang.org/reference/items/traits.html#generic-traits) +and +[uses them for operator overloading](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#default-generic-type-parameters-and-operator-overloading). +Note that Rust further supports defaults for those type parameters (such as +`Self`). + +[Rust uses the term "type parameters"](https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#clearer-trait-matching) +for both interface type parameters and associated types. The difference is that +interface parameters are "inputs" since they _determine_ which `impl` to use, +and associated types are "outputs" since they are determined _by_ the `impl`, +but play no role in selecting the `impl`. + +### Impl lookup + +Let's say you have some interface `I(T, U(V))` being implemented for some type +`A(B(C(D), E))`. To satisfy the orphan rule for coherence, that `impl` must be +defined in some library that must be imported in any code that looks up whether +that interface is implemented for that type. This requires that `impl` is +defined in the same library that defines the interface or one of the names +needed by the type. That is, the `impl` must be defined with one of `I`, `T`, +`U`, `V`, `A`, `B`, `C`, `D`, or `E`. We further require anything looking up +this `impl` to import the _definitions_ of all of those names. Seeing a forward +declaration of these names is insufficient, since you can presumably see forward +declarations without seeing an `impl` with the definition. This accomplishes a +few goals: + +- The compiler can check that there is only one definition of any `impl` that + is actually used, avoiding + [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) + problems. +- Every attempt to use an `impl` will see the exact same `impl`, making the + interpretation and semantics of code consistent no matter its context, in + accordance with the + [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md). +- Allowing the `impl` to be defined with either the interface or the type + addresses the + [expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). + +Note that [the rules for specialization](#lookup-resolution-and-specialization) +do allow there to be more than one `impl` to be defined for a type, as long as +one can unambiguously be picked as most specific. + +**References:** Implementation coherence is +[defined in terminology](terminology.md#coherence), and is +[a goal for Carbon](goals.md#coherence). More detail can be found in +[this appendix with the rationale and alternatives considered](appendix-coherence.md). + +### Parameterized structural interfaces + +We should also allow the [structural interface](#structural-interfaces) +construct to support parameters. Parameters would work the same way as for +regular, that is nominal or non-structural, interfaces. + +## Future work ### Constraints diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index fbb1e3bc6f000..0b5fb74c58be6 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -29,6 +29,10 @@ pointers to other design documents that dive deeper into individual topics. - [Combining interfaces](#combining-interfaces) - [Structural interfaces](#structural-interfaces) - [Type erasure](#type-erasure) + - [Adapting types](#adapting-types) + - [Interface input and output types](#interface-input-and-output-types) + - [Associated types](#associated-types) + - [Parameterized interfaces](#parameterized-interfaces) - [Future work](#future-work) @@ -55,7 +59,7 @@ Summary of how Carbon generics work: They are used to avoid writing specialized, near-duplicate code for similar situations. - Generics are written using _interfaces_ which have a name and describe - methods, functions, and other items for types to implement. + methods, functions, and other entities for types to implement. - Types must explicitly _implement_ interfaces to indicate that they support its functionality. A given type may implement an interface at most once. - Implementations may be part of the type's definition, in which case you can @@ -140,8 +144,8 @@ requirements were sufficient. #### Defining interfaces -Interfaces, then, have a name and describe methods, functions, and other items -for types to implement. +Interfaces, then, have a name and describe methods, functions, and other +entities for types to implement. Example: @@ -464,19 +468,100 @@ At that point, two erasures occur: of `PrintIt` you can cast a `CDCover as Printable` value back to `CDCover`. Inside of `PrintIt`, you can't cast `p` or `T` back to `CDCover`. +### Adapting types + +Carbon has a mechanism called "adapting types" to create new types that are +compatible with existing types but with different interface implementations. +This could be used to add or replace implementations, or define implementations +for reuse. + +In this example, we have multiple ways of sorting a collection of `Song` values. + +``` +class Song { ... } + +adapter SongByArtist extends Song { + impl as Comparable { ... } +} + +adapter SongByTitle extends Song { + impl as Comparable { ... } +} +``` + +Values of type `Song` may be cast to `SongByArtist` or `SongByTitle` to get a +specific sort order. + +### Interface input and output types + +[Associated types and interface parameters](terminology.md#interface-type-parameters-and-associated-types) +allow function signatures to vary with the implementing type. The biggest +difference between these is that associated types ("output types") may be +deduced from a type, and types can implement the same interface multiple times +with different interface parameters ("input types"). + +#### Associated types + +Expect types that vary in an interface to be associated types by default. Since +associated types may be deduced, they are more convenient to use. Imagine a +`Stack` interface. Different types implementing `Stack` will have different +element types: + +``` +interface Stack { + let ElementType:! Movable; + fn Push[addr me: Self*](value: ElementType); + fn Pop[addr me: Self*]() -> ElementType; + fn IsEmpty[addr me: Self*]() -> Bool; +} +``` + +`ElementType` is an associated type of the interface `Stack`. Types that +implement `Stack` give `ElementType` a specific value of some type implementing +`Movable`. Functions that accept a type implementing `Stack` can deduce the +`ElementType` from the stack type. + +``` +// ✅ This is allowed, since the type of the stack will determine +// `ElementType`. +fn PeekAtTopOfStack[StackType:! Stack](s: StackType*) + -> StackType.ElementType; +``` + +#### Parameterized interfaces + +Parameterized interfaces are commonly associated with overloaded operators. +Imagine an interface for determining if two values are equivalent that allows +those types to be different. An element in a hash map might have type +`Pair(String, i64)` that implements both `Equatable(String)` and +`Equatable(Pair(String, i64))`. + +``` +interface Equatable(T:! Type) { + fn IsEqual[me: Self](compare_to: T) -> Bool; +} +``` + +`T` is a parameter to interface `Equatable`. A type can implement `Equatable` +multiple times as long as each time it is with a different value of the `T` +parameter. Functions may accept types implementing `Equatable(i32)` or +`Equatable(f32)`. Functions can't accept types implementing `Equatable(T)` in +general, unless some other parameter determines `T`. + +``` +// ✅ This is allowed, since the value of `T` is determined by the +// `v` parameter. +fn FindInVector[T:! Type, U:! Equatable(T)](v: Vector(T), needle: U) + -> Optional(i32); + +// ❌ This is forbidden. Since `U` could implement `Equatable` +// multiple times, there is no way to determine the value for `T`. +// Contrast with `PeekAtTopOfStack` in the associated type example. +fn CompileError[T:! Type, U:! Equatable(T)](x: U) -> T; +``` + ## Future work -- Be able to have non-type generic parameters like the `UInt` size of an array - or tuple. -- A "newtype" mechanism called "adapting types" may be provided to create new - types that are compatible with existing types but with different interface - implementations. This could be used to add or replace implementations, or - define implementations for reuse. -- Associated types and interface parameters will be provided to allow function - signatures to vary with the implementing type. The biggest difference - between these is that associated types ("output types") may be deduced from - a type, and types can implement the same interface multiple times with - different interface parameters ("input types"). - Other kinds of constraints will be finalized. - Implementations can be parameterized to apply to multiple types. These implementations would be restricted to various conditions are true for the @@ -484,8 +569,8 @@ At that point, two erasures occur: specialization rule that picks the more specific one. - Support functions should have a way to accept types that types that vary at runtime. -- You should have the ability to mark items as `upcoming` or `deprecated` to - support evolution. +- You should have the ability to mark entities as `upcoming` or `deprecated` + to support evolution. - Types should be able to define overloads for operators by implementing standard interfaces. - There should be a way to provide default implementations of methods in diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 9f7e560b49e97..f040018db7736 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -24,6 +24,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Interface](#interface) - [Structural interfaces](#structural-interfaces) - [Nominal interfaces](#nominal-interfaces) +- [Associated entity](#associated-entity) - [Impls: Implementations of interfaces](#impls-implementations-of-interfaces) - [Compatible types](#compatible-types) - [Subtyping and casting](#subtyping-and-casting) @@ -39,7 +40,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Template specialization](#template-specialization) - [Generic specialization](#generic-specialization) - [Conditional conformance](#conditional-conformance) -- [Interface type parameters versus associated types](#interface-type-parameters-versus-associated-types) +- [Interface type parameters and associated types](#interface-type-parameters-and-associated-types) - [Type constraints](#type-constraints) - [Type-of-type](#type-of-type) @@ -51,7 +52,7 @@ Generally speaking, when we talk about either templates or a generics system, we are talking about generalizing some language construct by adding a parameter to it. Language constructs here primarily would include functions and types, but we may want to support parameterizing other language constructs like -[interfaces](#interface-type-parameters-versus-associated-types). +[interfaces](#interface-type-parameters-and-associated-types). This parameter broadens the scope of the language construct on an axis defined by that parameter, for example it could define a family of functions instead of @@ -296,14 +297,30 @@ We use the "structural" versus "nominal" terminology as a generalization of the same terms being used in a [subtyping context](https://en.wikipedia.org/wiki/Subtyping#Subtyping_schemes). +## Associated entity + +An _associated entity_ is a requirement in an interface that a type's +implementation of the interface must satisfy by having a matching member. A +requirement that the type define a value for a member constant is called an +_associated constant_, and similarly an _associated function_ or _associated +type_. + +Different types can satisfy an interface with different definitions for a given +member. These definitions are _associated_ with what type is implementing the +interface. An [impl](#impls-implementations-of-interfaces) defines what is +associated with the type for that interface. + +Rust uses the term +["associated item"](https://doc.rust-lang.org/reference/items/associated-items.html) +instead of associated entity. + ## Impls: Implementations of interfaces An _impl_ is an implementation of an interface for a specific type. It is the place where the function bodies are defined, values for associated types, etc. -are given. A given generics programming model may support default impls, named -impls, or both. Impls are mostly associated with nominal interfaces; structural -interfaces define conformance implicitly instead of by requiring an impl to be -defined. +are given. Impls are needed for [nominal interfaces](#nominal-interfaces); +[structural interfaces](#structural-interfaces) define conformance implicitly +instead of by requiring an impl to be defined. ## Compatible types @@ -514,18 +531,19 @@ that it always supports, but satisfies additional interfaces under some conditions on the type argument. For example: `Array(T)` might implement `Comparable` if `T` itself implements `Comparable`, using lexicographical order. -## Interface type parameters versus associated types +## Interface type parameters and associated types -Let's say you have 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 a type parameter; if it is an output, we say it is an associated type. +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). -Type parameter example: +Interface type parameter example: ``` -interface Stack(ElementType:! Type) +interface StackTP(ElementType:! Type) fn Push[addr me: Self*](value: ElementType); fn Pop[addr me: Self*]() -> ElementType; } @@ -534,8 +552,8 @@ interface Stack(ElementType:! Type) Associated type example: ``` -interface Stack { - let ElementType: Type; +interface StackAT { + let ElementType:! Type; fn Push[addr me: Self*](value: ElementType); fn Pop[addr me: Self*]() -> ElementType; } @@ -551,33 +569,80 @@ 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; + let IteratorType:! Iterator; ... fn Insert[addr me: Self*](position: IteratorType, value: ElementType); } class ListIterator(ElementType:! Type) { ... - impl Iterator; + impl as Iterator; } class List(ElementType:! Type) { // Iterator type is determined by the container type. - let IteratorType: Iterator = ListIterator(ElementType); + let IteratorType:! Iterator = ListIterator(ElementType); fn Insert[addr me: Self*](position: IteratorType, value: ElementType) { ... } - impl Container; + impl as Container; } ``` -Since type parameters are directly under the user's control, it is easier to -express things like "this type parameter is the same for all these interfaces", -and other type constraints. +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 +not be deduced in a function call. However, if the interface parameters are +specified, a type can only have a single implementation of the given interface. +This unique implementation choice determines the values of associated types. -If you have an interface with type parameters, there is a question of whether a -type can have multiple impls for different combinations of type parameters, or -if you can only have a single impl (in which case you can directly infer the -type parameters given just a type implementing the interface). You can always -infer associated types. +For example, we might have an interface that says how to perform addition with +another type: + +``` +interface Addable(T:! Type) { + let ResultType:! Type; + fn Add[me: Self](rhs: T) -> ResultType; +} +``` + +An `i32` value might support addition with `i32`, `u16`, and `f64` values. + +``` +impl i32 as Addable(i32) { + let ResultType:! Type = i32; + // ... +} +impl i32 as Addable(u16) { + let ResultType:! Type = i32; + // ... +} +impl i32 as Addable(f64) { + let ResultType:! Type = f64; + // ... +} +``` + +To write a generic function requiring a parameter to be `Addable`, 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 { + return x.Add(y); +} + +// ❌ This is forbidden, can't uniquely determine `T`. +fn CompileError[T:! Type, U:! Addable(T)](x: U) -> T; +``` + +Once the interface parameter can be determined, that determines the values for +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)` +// for `Apple`. +DoAdd(apple, orange); +``` ## Type constraints @@ -595,12 +660,12 @@ express, for example: element type. - An interface may define an associated type that needs to be constrained to implement some interfaces. -- This type parameter must be [compatible](#compatible-types) with another - type. You might use this to define alternate implementations of a single - interfaces, such as sorting order, for a single type. +- This type must be [compatible](#compatible-types) with another type. You + might use this to define alternate implementations of a single interfaces, + such as sorting order, for a single type. -Note that type constraints can be a restriction on one type parameter, or can -define a relationship between multiple type parameters. +Note that type constraints can be a restriction on one type parameter or +associated type, or can define a relationship between multiple types. ## Type-of-type diff --git a/proposals/README.md b/proposals/README.md index 468069aea5bc8..283c2e14a5245 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -68,5 +68,6 @@ request: - [0676 - `:!` generic syntax](p0676.md) - [0680 - And, or, not](p0680.md) - [0722 - Nominal classes and methods](p0722.md) +- [0731 - Generics details 2: adapters, associated types, parameterized interfaces](p0731.md) diff --git a/proposals/p0731.md b/proposals/p0731.md new file mode 100644 index 0000000000000..a8aa7c341200c --- /dev/null +++ b/proposals/p0731.md @@ -0,0 +1,384 @@ +# Generics details 2: adapters, associated types, parameterized interfaces + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/731) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + - [`adaptor` instead of `adapter`](#adaptor-instead-of-adapter) + - [Syntax for associated constants](#syntax-for-associated-constants) + - [Omitting types](#omitting-types) + - [Inferring associated types from method signatures](#inferring-associated-types-from-method-signatures) + - [Value patterns](#value-patterns) + - [Deduced interface parameters](#deduced-interface-parameters) + - [Rationale for the rejection](#rationale-for-the-rejection) + - [Impl lookup rules with deducible interface parameters](#impl-lookup-rules-with-deducible-interface-parameters) + - [Only associated types, no interface parameters](#only-associated-types-no-interface-parameters) + - [Others](#others) + + + +## Problem + +We want to Carbon to have a high quality generics feature that achieves the +goals set out in [#24](https://github.com/carbon-language/carbon-lang/pull/24). +This is too big to land in a single proposal. This proposal continues +[#553](https://github.com/carbon-language/carbon-lang/pull/553) defining the +details of: + +- adapters +- associated types and other constants +- parameterized interfaces + +## Background + +This is a follow on to these previous generics proposals: + +- [#24: Generics goals](https://github.com/carbon-language/carbon-lang/pull/24) +- [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447) +- [#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524) +- [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) + +The content for this proposal was extracted from a larger +[Generics combined draft proposal](https://github.com/carbon-language/carbon-lang/pull/36). + +## Proposal + +This is a proposal to add multiple sections to +[this design document on generics details](/docs/design/generics/details.md). + +## Rationale based on Carbon's goals + +Much of this rationale was captured in the +[Generics goals proposal](https://github.com/carbon-language/carbon-lang/pull/24). + +## Alternatives considered + +### `adaptor` instead of `adapter` + +We considered replacing the `adapter` keyword with the alternate spelling of +"adaptor". Both spellings can be used for the intended meaning, but the "-er" +spelling is more common in English text and in code. The final deciding factor +was that the +[GoF Design Patterns book](https://en.wikipedia.org/wiki/Design_Patterns) spells +the ["adapter pattern"](https://en.wikipedia.org/wiki/Adapter_pattern) with the +["-er" spelling](https://springframework.guru/gang-of-four-design-patterns/adapter-pattern/). + +### Syntax for associated constants + +Issue +[#739: Associated type syntax](https://github.com/carbon-language/carbon-lang/issues/739) +decided that the syntax for assigning a value to an associated constant, such as +an associated type. + +The decision was to use `let` with `:!` to express that these are compile-time +values, matching the use in classes described in proposal +[#772](p0722.md#let-constants). + +``` +interface Stack { + let ElementType:! Type; + fn Push[addr me: Self*](value: ElementType); + ... +} + +class DynamicArray(T:! Type) { + ... + impl as Stack { + let ElementType:! Type = T; + fn Push[addr me: Self*](value: ElementType); + ... + } +} +``` + +One advantage was this opened the door for a type to satisfy the associated +types of two interfaces with the same name with a single `let` declaration using +constraints satisfying the requirements of both interfaces. + +This type can be replaced with `auto`, to have it determined automatically. + +``` +class DynamicArray(T:! Type) { + ... + impl as Stack { + let ElementType:! auto = T; + fn Push[addr me: Self*](value: ElementType); + ... + } +} +``` + +This would avoid needing to change the `impl` when the constraints in the +interface changed as long as the value to the right of the `=` satisfied the new +constraints. Otherwise if the constraints are being weakened, first functions +relying on the capabilities being removed would have to change, then they would +be changed in the interface, and finally the implementations for types. If the +constraints are being strengthened, the implementations for types would have to +change first followed by the interface. + +#### Omitting types + +We also considered omitting the type in the `impl`, always using the type +declared in the interface. + +``` +class DynamicArray(T:! Type) { + ... + impl as Stack { + let ElementType = T; + fn Push[addr me: Self*](value: ElementType); + ... + } +} +``` + +This would provide the advantage of reducing the number of changes when changing +the constraint specified in the interface. If the constraints were being +weakened, then functions that used the capability that was being removed would +break or need to be modified. If the constraints were being strengthened, then +only type implementations that didn't satisfy the new constraints would break or +need to be modified. + +The biggest difference from the selected option is when adding a constraint. In +that case the selected option would have more churn, because all implementations +would be updated even if they already satisfied the new constraint. This comes +with the advantage of making it easier _incrementally enforce_ greater +constraints. + +On the whole, it seems like both could be made to work. You could explicitly +specify constraints with this option by using an alias to a normal +`let ..:! TypeOfType` declaration that has extra constraints. Conversely, you +can specify `auto` as the constraints in the selected option. + +But on balance, it seemed better to try putting the explicit constraints into +the implementations so that we have more tools to incrementally roll out changes +to interface constraints even though those rollouts will as a consequence be +more noisy in some cases. If experience shows that this is a really bad +tradeoff, we should revisit it. + +#### Inferring associated types from method signatures + +The last option considered is used by Swift. +[Swift allows the value of an associated type to be omitted when it can be determined from the method signatures in the implementation](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190). +For the above example, this would mean figuring out `ElementType == T` from +context: + +``` +class DynamicArray(T:! Type) { + ... + impl as Stack { + // Not needed: let ElementType:! Type = T; + fn Push[addr me: Self*](value: T); + ... + } +} +``` + +One benefit is that it allows an interface to evolve by adding an associated +type, without having to then modify all implementations of that interface. + +One concern is this might be a little more complicated in the presence of method +overloads with [default implementations](interface-defaults), since it might not +be clear how they should match up, as in this example: + +``` +interface Has2OverloadsWithDefaults { + let T:! StackAssociatedType; + fn F[me: Self](x: DynamicArray(T), y: T) { ... } + fn F[me: Self](x: T, y: T.ElementType) { ... } +} + +class S { + impl as Has2OverloadsWithDefaults { + // Unclear if T == DynamicArray(Int) or + // T == DynamicArray(DynamicArray(Int)). + fn F[me: Self]( + x: DynamicArray(DynamicArray(Int)), + y: DynamicArray(Int)) { ... } + } +} +``` + +Not to say this can't be resolved, but it does add complexity. +[Swift considered](https://github.com/apple/swift/blob/main/docs/GenericsManifesto.md#associated-type-inference) +removing this feature because it was the one thing in Swift that required global +type inference, which they otherwise avoided. They +[ultimately decided to keep the feature](https://github.com/apple/swift-evolution/blob/main/proposals/0108-remove-assoctype-inference.md). + +This option was only very briefly discussed and not preferred because: + +- It came with complexity of inference. +- It seemed unnecessary. + +### Value patterns + +We considered an alternative to the `type_of` approach from +[the parameterized interfaces section](/docs/design/generics/details.md#parameterized-interfaces) +for binding `T` to a type mentioned later in the parameter list. We could +instead allow functions to have value patterns without a `:`, as in: + +``` +fn PeekAtTopOfStackParameterized + [T:! Type, StackType:! StackParameterized(T)] + (s: StackType*, T) -> T { ... } +``` + +However, we don't want to allow value patterns more generally so we can reject +declarations like `fn F(Int)` when users almost certainly meant `fn F(i: Int)`. + +### Deduced interface parameters + +The Carbon team considered and then rejected the idea that we would have two +kinds of interface parameters. "Multi" parameters would work as described in the +[detailed design document](/docs/design/generics/details.md#parameterized-interfaces). +"Deducible" type parameters would only allow one implementation of an interface, +not one per interface & type parameter combination. These deducible type +parameters could be inferred like +[associated types](/docs/design/generics/details.md#associated-types) are. For +example, we could make a `Stack` interface that took a deducible `ElementType` +parameter. You would only be able to implement that interface once for a type, +which would allow you to infer the `ElementType` parameter like so: + +``` +fn PeekAtTopOfStack[ElementType:! Type, StackType:! Stack(ElementType)] + (s: StackType*) -> ElementType { ... } +``` + +This can result in more concise code for interfaces where you generally need to +talk about some parameter anytime you use that interface. For example, +`NTuple(N, type)` is much shorter without having to specify names with the +arguments. + +#### Rationale for the rejection + +- Having only one type of parameter simplifies the language. +- Multi parameters express something we need, while deducible parameters can + always be changed to associated types. +- One implementation per interface & type parameter combination is more + consistent with other parameterized constructs in Carbon. For example, + parameterized types `Foo(A)` and `Foo(B)` are distinct, unconnected types. +- It would be hard to give clear guidance on when to use associated types + versus deducible type parameters, since which is best for a particular use + is more of a subtle judgement call. +- Deducible parameters in structural interfaces require additional rules to + ensure they can be deduced unambiguously. + +In addition, deducible interface parameters would complicate the lookup rules +for impls. + +##### Impl lookup rules with deducible interface parameters + +Interface implementation is Carbon's only language construct that allows open +extension, and this sort of open extension is needed to address the "expression +problem" in programming language design. However, we need to limit which +libraries can implement an interface for a type so we can be guaranteed to see +the implementation when we try and use it. + +So the question becomes: can we allow an implementation of a parameterized +interface `I(T)` for a type `A` to be in the same library as `T`, or can it only +be provided with `I` or `A`? The answer is "yes" if `T` is "multi" and "no" if +`T` is deducible. + +The problem with defining the implementation with a deducible `T` is that it +would allow users to violate +[coherence](/docs/design/generics/goals.md#coherence). Consider this collection +of libraries, where there are implementations for an interface `I(T)` for a type +`A`, and those implementations are in the libraries defining the type parameter: + +``` +package X library "I and A" api; + +interface I(Type:$ T) { ... } + +struct A { ... } +``` + +``` +package Y library "T1" api; + +import X library "I and A"; + +struct T1 { ... } + +// Type `X.A` has an implementation for `X.I(T)` for `T == Y.T1`. +impl X.I(T1) for X.A { ... } +``` + +``` +package Z library "T2" api; + +import X library "I and A"; + +struct T2 { ... } + +// Type `X.A` has an implementation for `X.I(T)` for `T == Z.T2`. +impl X.I(T2) for X.A { ... } +``` + +``` +package Main api; + +import X library "I and A"; +// Consider what happens if we include different combinations +// of the following two statements: +// import Y library "T1"; +// import Z library "T2"; + +// Function `F` is called with value `a` with type `U`, +// where `U` implements interface `X.I(T)` for some type `T`. +fn F[Type:$ T, X.I(T):$ U](U:$ a) { ... } + +fn Main() { + var X.A: a = X.A.Init(); + F(a); +} +``` + +(In the example, each library is in a different package, but the packages are +not the important part here.) + +The `F(a)` call triggers a lookup for implementations of the interface `X.I(T)` +for some `T`. There exists such implementations in both libraries `Y.T1` and +`Z.T2` for different values of `T`. This has a number of sad consequences: + +- "Import what you use" is hard to measure: libraries `Y.T1` and `Z.T2` are + important and used even though `Y` and `Z` are not mentioned outside the + `import` statement. +- The call `F(a)` has different interpretations depending on what libraries + are imported: + - If neither is imported, it is an error. + - If both are imported, it is ambiguous. + - If only one is imported, you get totally different code executed + depending on which it is. +- We have no way of enforcing a "one implementation per interface" rule that + would prevent the call to `F` from being ambiguous. + +Basically, there is nothing guaranteeing that we import libraries defining the +types that are used as interface parameters if we allow the interface parameters +to be deduced. + +### Only associated types, no interface parameters + +This is what Swift does, but doesn't allow us to use interfaces to express +operator overloads. For example, a vector should be able to be added to either a +vector or a point. So we follow Rust, which has trait parameters in addition to +associated types and uses them to define the behavior of operators. + +### Others + +Other alternatives considered will be in a future proposal. Some of them can be +seen in a rough form in +[#36](https://github.com/carbon-language/carbon-lang/pull/36).