Procházet zdrojové kódy

Update generics overview (#3061)

This reflects changes from a number of approved proposals:

- #2138 : "generic" -> "checked generic", "template" -> "template
generic"
- #2360 : "type", "facet type", "facet". Note: I am not using the term
"generic type" from #2360 since that meaning conflicts with the
generally accepted meaning of "generic type" of a type with a
compile-time parameter.
- #2760 / #2770 : internal/external impl -> extending impl
- #2964 : "symbolic constant" and "template constant"

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b před 2 roky
rodič
revize
e15bdbb36c
1 změnil soubory, kde provedl 139 přidání a 105 odebrání
  1. 139 105
      docs/design/generics/overview.md

+ 139 - 105
docs/design/generics/overview.md

@@ -21,17 +21,17 @@ pointers to other design documents that dive deeper into individual topics.
         -   [Contrast with templates](#contrast-with-templates)
     -   [Implementing interfaces](#implementing-interfaces)
         -   [Accessing members of interfaces](#accessing-members-of-interfaces)
-    -   [Type-of-types](#type-of-types)
+    -   [Facet types](#facet-types)
     -   [Generic functions](#generic-functions)
         -   [Deduced parameters](#deduced-parameters)
-        -   [Generic type parameters](#generic-type-parameters)
+        -   [Facet parameters](#facet-parameters)
     -   [Requiring or extending another interface](#requiring-or-extending-another-interface)
     -   [Combining interfaces](#combining-interfaces)
         -   [Named constraints](#named-constraints)
         -   [Type erasure](#type-erasure)
     -   [Adapting types](#adapting-types)
-    -   [Interface input and output types](#interface-input-and-output-types)
-        -   [Associated types](#associated-types)
+    -   [Interface inputs and outputs](#interface-inputs-and-outputs)
+        -   [Associated constants](#associated-constants)
         -   [Parameterized interfaces](#parameterized-interfaces)
     -   [Constraints](#constraints)
     -   [Parameterized impl declarations](#parameterized-impl-declarations)
@@ -43,8 +43,20 @@ pointers to other design documents that dive deeper into individual topics.
 
 ## Goals
 
-The goal of Carbon generics is to provide an alternative to Carbon (or C++)
-templates. Generics in this form should provide many advantages, including:
+Carbon [generics](terminology.md#generic-means-compile-time-parameterized)
+supports generalizing code to apply to more situations by adding compile-time
+parameters, allowing
+[generic programming](https://en.wikipedia.org/wiki/Generic_programming). Carbon
+supports both
+[checked and template](terminology.md#checked-versus-template-parameters)
+generics.
+
+Template generics provide a similar model to C++ templates, to help with interop
+and migration. They can be more convenient to write, and support some use cases,
+like [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming), that are
+difficult with checked generics.
+
+Checked generics are an alternative that has advantages including:
 
 -   Function calls and bodies are checked independently against the function
     signatures.
@@ -52,6 +64,9 @@ templates. Generics in this form should provide many advantages, including:
 -   Fast builds, particularly development builds.
 -   Support for both static and dynamic dispatch.
 
+Checked generics do have some restrictions, but are expected to be more
+appropriate at public API boundaries than templates.
+
 For more detail, see [the detailed discussion of generics goals](goals.md) and
 [generics terminology](terminology.md).
 
@@ -59,35 +74,47 @@ For more detail, see [the detailed discussion of generics goals](goals.md) and
 
 Summary of how Carbon generics work:
 
--   _Generics_ are parameterized functions and types that can apply generally.
-    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 entities for types to implement.
+-   _Generics_ are compile-time parameterized functions, types, and other
+    language constructs. Those parameters allow a single definition to apply
+    more generally. They are used to avoid writing specialized, near-duplicate
+    code for similar situations.
+-   The definition of a _checked_ generic is typechecked once, without having to
+    know the specific argument values of the generic parameters it is
+    instantiated with. Typechecking the definition of a checked generic requires
+    a precise contract specifying the requirements on the argument values.
+-   For parameters that will be used as types, those requirements are written
+    using _interfaces_. Interfaces have a name and describe 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
-    directly call the interface's methods on those types. Or, they may be
-    external, in which case the implementation is allowed to be defined in the
-    library defining the interface.
--   Interfaces are used as the type of a generic type parameter, acting as a
-    _type-of-type_. Type-of-types in general specify the capabilities and
-    requirements of the type. Types define specific implementations of those
-    capabilities. Inside such a generic function, the API of the type is
-    [erased](terminology.md#type-erasure), except for the names defined in the
-    type-of-type.
--   _Deduced parameters_ are parameters whose values are determined by the
-    values and (most commonly) the types of the explicit arguments. Generic type
-    parameters are typically deduced.
--   A function with a generic type parameter can have the same function body as
-    an unparameterized one. Functions can freely mix generic, template, and
-    regular parameters.
+    their functionality. A given type may implement an interface at most once.
+-   Implementations may be declared inline in the body of a class definition or
+    out-of-line.
+-   Types may _extend_ an implementation declared inline, in which case you can
+    directly call the interface's methods on those types.
+-   Out-of-line implementations may be defined in the library defining the
+    interface as an alternative to the type, or
+    [the library defining a type argument](#parameterized-impl-declarations).
+-   Interfaces may be used as the type of a generic parameter. Interfaces are
+    _facet types_, whose values are the subset of all types that implement the
+    interface. Facet types in general specify the capabilities and requirements
+    of the type. The value of a interface is called a _facet_. Facets are not
+    types, but are usable as types.
+-   With a template generic, the concrete argument value used by the caller is
+    used for name lookup and typechecking. With checked generics, that is all
+    done with the declared restrictions expressed as the types of bindings in
+    the declaration. Inside the body of a checked generic with a facet
+    parameter, the API of the facet is just the names defined by the facet type.
+-   _Deduced parameters_ are parameters whose values are determined by the types
+    of the explicit arguments. Generic facet parameters are typically deduced.
+-   A function with a generic parameter can have the same function body as an
+    unparameterized one. Functions can freely mix checked, template, and regular
+    parameters.
 -   Interfaces can require other interfaces be implemented.
 -   Interfaces can [extend](terminology.md#extending-an-interface) required
     interfaces.
--   The `&` operation on type-of-types allows you conveniently combine
-    interfaces. It gives you all the names that don't conflict.
--   You may also declare a new type-of-type directly using
+-   The `&` operation on facet types allows you conveniently combine interfaces.
+    It gives you all the names that don't conflict.
+-   You may also declare a new facet type directly using
     ["named constraints"](terminology.md#named-constraints). Named constraints
     can express requirements that multiple interfaces be implemented, and give
     you control over how name conflicts are handled.
@@ -114,8 +141,9 @@ elements:
 fn SortVector(T:! Comparable, a: Vector(T)*) { ... }
 ```
 
-The syntax above adds a `!` to indicate that the parameter named `T` is generic
-and the caller will have to provide a value known at compile time.
+The syntax above adds a `!` to indicate that the parameter named `T` is
+compile-time. By default compile-time parameters are _checked_, the `template`
+keyword may be added to make it a _template generic_.
 
 Given an `i32` vector `iv`, `SortVector(i32, &iv)` is equivalent to
 `SortInt32Vector(&iv)`. Similarly for a `String` vector `sv`,
@@ -128,14 +156,16 @@ This ability to generalize makes `SortVector` a _generic_.
 ### Interfaces
 
 The `SortVector` function requires a definition of `Comparable`, with the goal
-that the compiler can:
+that the compiler can perform checking. This has two pieces:
 
--   completely type check a generic definition without information from where
-    it's called.
--   completely type check a call to a generic with information only from the
-    function's signature, and not from its body.
+-   definition checking: completely type check a checked-generic definition
+    without information from calls;
+-   encapsulation: completely type check a call to a generic with information
+    only from the function's signature, and not from its body. For Rust, this is
+    called
+    "[Rust's Golden Rule](https://steveklabnik.com/writing/rusts-golden-rule)."
 
-In this example, then, `Comparable` is an _interface_.
+In this example, `Comparable` is an _interface_.
 
 Interfaces describe all the requirements needed for the type `T`. Given that the
 compiler knows `T` satisfies those requirements, it can type check the body of
@@ -174,13 +204,13 @@ an interface.
 
 #### Contrast with templates
 
-Contrast these generics with a C++ template, where the compiler may be able to
-do some checking given a function definition, but more checking of the
-definition is required after seeing the call sites once all the
+Contrast these checked generics with a Carbon or C++ template, where the
+compiler may be able to do some checking given a function definition, but more
+checking of the definition is required after seeing the call sites once all the
 [instantiations](terminology.md#instantiation) are known.
 
 Note: [Generics terminology](terminology.md) goes into more detail about the
-[differences between generics and templates](terminology.md#checked-versus-template-parameters).
+[differences between checked and template generic parameters](terminology.md#checked-versus-template-parameters).
 
 ### Implementing interfaces
 
@@ -225,24 +255,24 @@ impl Song as Comparable {
 Implementations may be defined within the class definition itself or
 out-of-line. Implementations may optionally start with the `extend` keyword to
 say the members of the interface are also members of the class, which may only
-be used in a class scope. Otherwise, implementations may be defined in the
-library defining either the class or the interface.
+be used in a class scope. Out-of-line implementations may be defined in the
+library defining the class, the interface, or
+[a type argument](#parameterized-impl-declarations).
 
 #### Accessing members of interfaces
 
-The methods of an interface implemented internally within the class definition
-may be called with the
+Methods from an interface that a class extends may be called with the
 [simple member access syntax](terminology.md#simple-member-access). Methods of
 all implemented interfaces may be called with a
 [qualified member access expression](terminology.md#qualified-member-access-expression),
-whether they are defined internally or externally.
+whether the class extends them or not.
 
 ```
 var song: Song;
 // `song.Print()` is allowed, unlike `song.Play()`.
 song.Print();
-// `Less` is defined in `Comparable`, which is
-// implemented externally for `Song`
+// `Less` is defined in `Comparable`, which `Song`
+// does not extend the implementation of.
 song.(Comparable.Less)(song);
 // Can also call `Print` using a qualified member
 // access expression, using the compound member access
@@ -250,28 +280,28 @@ song.(Comparable.Less)(song);
 song.(Printable.Print)();
 ```
 
-### Type-of-types
+### Facet types
 
 To type check a function, the compiler needs to be able to verify that uses of a
 value match the capabilities of the value's type. In `SortVector`, the parameter
-`T` is a type, but that type is a generic parameter. That means that the
-specific type value assigned to `T` is not known when type checking the
-`SortVector` function. Instead it is the constraints on `T` that let the
-compiler know what operations may be performed on values of type `T`. Those
-constraints are represented by the type of `T`, a
-[**_type-of-type_**](terminology.md#facet-type).
-
-In general, a type-of-type describes the capabilities of a type, while a type
+`T` is a type, but that type is a checked-generic, or _symbolic_, parameter.
+That means that the specific type value assigned to `T` is not known when type
+checking the `SortVector` function. Instead it is the constraints on `T` that
+let the compiler know what operations may be performed on values of type `T`.
+Those constraints are represented by the type of `T`, a
+[**_facet type_**](terminology.md#facet-type).
+
+In general, a facet type describes the capabilities of a type, while a type
 defines specific implementations of those capabilities. An interface, like
-`Comparable`, may be used as a type-of-type. In that case, the constraint on the
+`Comparable`, may be used as a facet type. In that case, the constraint on the
 type is that it must implement the interface `Comparable`.
 
-A type-of-type also defines a set of names and a mapping to corresponding
+A facet type also defines a set of names and a mapping to corresponding
 qualified names. Those names are used for
 [simple member lookup](terminology.md#simple-member-access) in scopes where the
 value of the type is not known, such as when the type is a generic parameter.
 
-You may combine interfaces into new type-of-types using
+You may combine interfaces into new facet types using
 [the `&` operator](#combining-interfaces) or
 [named constraints](#named-constraints).
 
@@ -314,9 +344,9 @@ call site.
 fn Illegal[T:! type, U:! type](x: T) -> U { ... }
 ```
 
-#### Generic type parameters
+#### Facet parameters
 
-A function with a generic type parameter can have the same function body as an
+A function with a facet parameter can have the same function body as an
 unparameterized one.
 
 ```
@@ -329,20 +359,24 @@ fn PrintIt(p: Song*) {
 }
 ```
 
-Inside the function body, you can treat the generic type parameter just like any
-other type. There is no need to refer to or access generic parameters
-differently because they are defined as generic, as long as you only refer to
-the names defined by [type-of-type](#type-of-types) for the type parameter.
+Inside the function body, you can treat the facet parameter just like any other
+type. There is no need to refer to or access generic parameters differently
+because they are defined as generic, as long as you only refer to the names
+defined by [facet type](#facet-types) for the facet parameter.
 
-You may also refer to any of the methods of interfaces required by the
-type-of-type using a
-[qualified member access expression](#accessing-members-of-interfaces), as shown
-in the following sections.
+You may also refer to any of the methods of interfaces required by the facet
+type using a
+[qualified member access expression](#accessing-members-of-interfaces).
 
-A function can have a mix of generic, template, and regular parameters.
-Likewise, it's allowed to pass a template or generic value to a generic or
-regular parameter. _However, passing a generic value to a template parameter is
-future work._
+A function can have a mix of checked, template, and regular parameters. Each
+kind of parameter is defined using a different syntax: a checked parameter is
+uses a symbolic binding pattern, a template parameter uses a template binding
+pattern, and a regular parameter uses a runtime binding pattern. Likewise, it's
+allowed to pass a symbolic or template constant value to a checked or regular
+parameter. _We have decided to support passing a symbolic value to a template
+parameter, see
+[leads issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153),
+but incorporating it into the design is future work._
 
 ### Requiring or extending another interface
 
@@ -398,8 +432,8 @@ k.IsEqual(k);
 
 ### Combining interfaces
 
-The `&` operation on type-of-types allows you conveniently combine interfaces.
-It gives you all the names that don't conflict.
+The `&` operation on facet types allows you conveniently combine interfaces. It
+gives you all the names that don't conflict.
 
 ```
 interface Renderable {
@@ -431,7 +465,7 @@ fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) {
 
 #### Named constraints
 
-You may also declare a new type-of-type directly using
+You may also declare a new facet type directly using
 ["named constraints"](terminology.md#named-constraints). Named constraints can
 express requirements that multiple interfaces be implemented, and give you
 control over how name conflicts are handled. Named constraints have other
@@ -461,12 +495,11 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) {
 
 #### Type erasure
 
-Inside a generic function, the API of a type argument is
-[erased](terminology.md#type-erasure) except for the names defined in the
-type-of-type. An equivalent model is to say an
-[archetype](terminology.md#archetype) is used for type checking and name lookup
-when the actual type is not known in that scope. The archetype has members
-dictated by the type-of-type.
+Inside a generic function, the API of a facet argument is
+[erased](terminology.md#type-erasure) except for the names defined in the facet
+type. An equivalent model is to say an [archetype](terminology.md#archetype) is
+used for type checking and name lookup when the actual type is not known in that
+scope. The archetype has members dictated by the facet type.
 
 For example: If there were a class `CDCover` defined this way:
 
@@ -517,20 +550,20 @@ class SongByTitle {
 Values of type `Song` may be cast to `SongByArtist` or `SongByTitle` to get a
 specific sort order.
 
-### Interface input and output types
+### Interface inputs and outputs
 
-[Associated types and interface parameters](terminology.md#interface-parameters-and-associated-constants)
+[Associated constants and interface parameters](terminology.md#interface-parameters-and-associated-constants)
 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").
+difference between these is that associated constants ("outputs") may be deduced
+from a type, and types can implement the same interface multiple times with
+different interface parameters ("inputs").
 
-#### Associated types
+#### Associated constants
 
-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:
+Expect parts of function signatures that vary in an interface to be associated
+constants by default. Since associated constants may be deduced, they are more
+convenient to use. Imagine a `Stack` interface. Different types implementing
+`Stack` will have different element types:
 
 ```
 interface Stack {
@@ -541,10 +574,10 @@ interface Stack {
 }
 ```
 
-`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.
+`ElementType` is an associated constant of the interface `Stack`. Types that
+implement `Stack` give `ElementType` a specific value that is some type (really,
+facet) 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
@@ -581,16 +614,17 @@ fn FindInVector[T:! type, U:! Equatable(T)](v: Vector(T), needle: U)
 
 // ❌ 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.
+// Contrast with `PeekAtTopOfStack` in the associated constant
+// example.
 fn CompileError[T:! type, U:! Equatable(T)](x: U) -> T;
 ```
 
 ### Constraints
 
-Type-of-types can be further constrained using a `where` clause:
+Facet types can be further constrained using a `where` clause:
 
 ```
-fn FindFirstPrime[T:! Container where .Element == i32]
+fn FindFirstPrime[T:! Container where .Element = i32]
     (c: T, i: i32) -> Optional(i32) {
   // The elements of `c` have type `T.Element`, which is `i32`.
   ...
@@ -608,7 +642,7 @@ increase the knowledge that may be used in the body of the function to operate
 on values of those types.
 
 Constraints are also used when implementing an interface to specify the values
-of associated types (and other associated constants).
+of associated constants.
 
 ```
 class Vector(T:! Movable) {
@@ -623,8 +657,8 @@ parameters can have constraints to restrict when the implementation applies.
 When multiple implementations apply, there is a rule to pick which one is
 considered the most specific:
 
--   All type parameters in each `impl` declaration are replaced with question
-    marks `?`. This is called the type structure of the `impl` declaration.
+-   All parameters in each `impl` declaration are replaced with question marks
+    `?`. This is called the type structure of the `impl` declaration.
 -   Given two type structures, find the first difference when read from
     left-to-right. The one with a `?` is less specific, the one with a concrete
     type name in that position is more specific.