Преглед на файлове

Generic details 12: parameterized types (#1146)

This proposal has three main contributions:
- Types with generic parameters have an identity that consists of the types names plus the values of those parameters.
- The parameters of a type may be deduced from a function's argument.
- Types with generic parameters do not support specialization. Instead, a type can delegate to an interface to opt in to allowing specific customization points.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b преди 4 години
родител
ревизия
b9538129a2
променени са 2 файла, в които са добавени 251 реда и са изтрити 6 реда
  1. 166 6
      docs/design/generics/details.md
  2. 85 0
      proposals/p1146.md

+ 166 - 6
docs/design/generics/details.md

@@ -106,6 +106,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Operator overloading](#operator-overloading)
     -   [Binary operators](#binary-operators)
     -   [`like` operator for implicit conversions](#like-operator-for-implicit-conversions)
+-   [Parameterized types](#parameterized-types)
+    -   [Specialization](#specialization)
 -   [Future work](#future-work)
     -   [Dynamic types](#dynamic-types)
         -   [Runtime type parameters](#runtime-type-parameters)
@@ -118,7 +120,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
         -   [Generic associated types](#generic-associated-types)
         -   [Higher-ranked types](#higher-ranked-types)
     -   [Field requirements](#field-requirements)
-    -   [Generic type specialization](#generic-type-specialization)
     -   [Bridge for C++ customization points](#bridge-for-c-customization-points)
     -   [Variadic arguments](#variadic-arguments)
     -   [Range constraints on generic integers](#range-constraints-on-generic-integers)
@@ -5154,6 +5155,169 @@ external impl [T:! IntLike] like T
     as MultipliableWith(like T) where .Result = T;
 ```
 
+## Parameterized types
+
+Types may have generic parameters. Those parameters may be used to specify types
+in the declarations of its members, such as data fields, member functions, and
+even interfaces being implemented. For example, a container type might be
+parameterized by the type of its elements:
+
+```
+class HashMap(
+    KeyType:! Hashable & EqualityComparable & Movable,
+    ValueType:! Movable) {
+  // `Self` is `HashMap(KeyType, ValueType)`.
+
+  // Parameters may be used in function signatures.
+  fn Insert[addr me: Self*](k: KeyType, v: ValueType);
+
+  // Parameters may be used in field types.
+  private var buckets: Vector((KeyType, ValueType));
+
+  // Parameters may be used in interfaces implemented.
+  impl as Container where .ElementType = (KeyType, ValueType);
+  impl as ComparableWith(HashMap(KeyType, ValueType));
+}
+```
+
+Note that, unlike functions, every parameter to a type must either be generic or
+template, using `:!` or `template...:!`, not dynamic, with a plain `:`.
+
+Two types are the same if they have the same name and the same arguments.
+Carbon's [manual type equality](#manual-type-equality) approach means that the
+compiler may not always be able to tell when two type expressions are equal
+without help from the user, in the form of
+[`observe` declarations](#observe-declarations). This means Carbon will not in
+general be able to determine when types are unequal.
+
+Unlike an [interface's parameters](#parameterized-interfaces), a type's
+parameters may be [deduced](terminology.md#deduced-parameter), as in:
+
+```
+fn ContainsKey[KeyType:! Movable, ValueType:! Movable]
+    (haystack: HashMap(KeyType, ValueType), needle: KeyType)
+    -> bool { ... }
+fn MyMapContains(s: String) {
+  var map: HashMap(String, i32) = (("foo", 3), ("bar", 5));
+  // ✅ Deduces `KeyType` = `String` from the types of both arguments.
+  // Deduces `ValueType` = `i32` from the type of the first argument.
+  return ContainsKey(map, s);
+}
+```
+
+Note that restrictions on the type's parameters from the type's declaration can
+be [implied constraints](#implied-constraints) on the function's parameters.
+
+### Specialization
+
+[Specialization](terminology.md#generic-specialization) is used to improve
+performance in specific cases when a general strategy would be inefficient. For
+example, you might use
+[binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) for
+containers that support random access and keep their contents in sorted order
+but [linear search](https://en.wikipedia.org/wiki/Linear_search) in other cases.
+Types, like functions, may not be specialized directly in Carbon. This effect
+can be achieved, however, through delegation.
+
+For example, imagine we have a parameterized class `Optional(T)` that has a
+default storage strategy that works for all `T`, but for some types we have a
+more efficient approach. For pointers we can use a
+[null value](https://en.wikipedia.org/wiki/Null_pointer) to represent "no
+pointer", and for booleans we can support `True`, `False`, and `None` in a
+single byte. Clients of the optional library may want to add additional
+specializations for their own types. We make an interface that represents "the
+storage of `Optional(T)` for type `T`," written here as `OptionalStorage`:
+
+```
+interface OptionalStorage {
+  let Storage:! Type;
+  fn MakeNone() -> Storage;
+  fn Make(x: Self) -> Storage;
+  fn IsNone(x: Storage) -> bool;
+  fn Unwrap(x: Storage) -> Self;
+}
+```
+
+The default implementation of this interface is provided by a
+[blanket implementation](#blanket-impls):
+
+```
+// Default blanket implementation
+impl [T:! Movable] T as OptionalStorage
+    where .Storage = (bool, T) {
+  ...
+}
+```
+
+This implementation can then be
+[specialized](#lookup-resolution-and-specialization) for more specific type
+patterns:
+
+```
+// Specialization for pointers, using nullptr == None
+final external impl [T:! Type] T* as OptionalStorage
+    where .Storage = Array(Byte, sizeof(T*)) {
+  ...
+}
+// Specialization for type `bool`.
+final external impl bool as OptionalStorage
+    where .Storage = Byte {
+  ...
+}
+```
+
+Further, libraries can implement `OptionalStorage` for their own types, assuming
+the interface is not marked `private`. Then the implementation of `Optional(T)`
+can delegate to `OptionalStorage` for anything that can vary with `T`:
+
+```
+class Optional(T:! Movable) {
+  fn None() -> Self {
+    return {.storage = T.(OptionalStorage.MakeNone)()};
+  }
+  fn Some(x: T) -> Self {
+    return {.storage = T.(OptionalStorage.Make)(x)};
+  }
+  ...
+  private var storage: T.(OptionalStorage.Storage);
+}
+```
+
+Note that the constraint on `T` is just `Movable`, not
+`Movable & OptionalStorage`, since the `Movable` requirement is
+[sufficient to guarantee](#lookup-resolution-and-specialization) that some
+implementation of `OptionalStorage` exists for `T`. Carbon does not require
+callers of `Optional`, even generic callers, to specify that the argument type
+implements `OptionalStorage`:
+
+```
+// ✅ Allowed: `T` just needs to be `Movable` to form `Optional(T)`.
+//             A `T:! OptionalStorage` constraint is not required.
+fn First[T:! Movable & Eq](v: Vector(T)) -> Optional(T);
+```
+
+Adding `OptionalStorage` to the constraints on the parameter to `Optional` would
+obscure what types can be used as arguments. `OptionalStorage` is an
+implementation detail of `Optional` and need not appear in its public API.
+
+In this example, a `let` is used to avoid repeating `OptionalStorage` in the
+definition of `Optional`, since it has no name conflicts with the members of
+`Movable`:
+
+```
+class Optional(T:! Movable) {
+  private let U:! Movable & OptionalStorage = T;
+  fn None() -> Self {
+    return {.storage = U.MakeNone()};
+  }
+  fn Some(x: T) -> Self {
+    return {.storage = u.Make(x)};
+  }
+  ...
+  private var storage: U.Storage;
+}
+```
+
 ## Future work
 
 ### Dynamic types
@@ -5230,11 +5394,6 @@ implementing type has a particular field. This would be to match the
 expressivity of inheritance, which can express "all subtypes start with this
 list of fields."
 
-### Generic type specialization
-
-See [generic specialization](terminology.md#generic-specialization) for a
-description of what this might involve.
-
 ### Bridge for C++ customization points
 
 See details in [the goals document](goals.md#bridge-for-c-customization-points).
@@ -5273,3 +5432,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
 -   [#1013: Generics: Set associated constants using `where` constraints](https://github.com/carbon-language/carbon-lang/pull/1013)
 -   [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084)
 -   [#1144: Generic details 11: operator overloading](https://github.com/carbon-language/carbon-lang/pull/1144)
+-   [#1146: Generic details 12: parameterized types](https://github.com/carbon-language/carbon-lang/pull/1146)

+ 85 - 0
proposals/p1146.md

@@ -0,0 +1,85 @@
+# Generic details 12: parameterized types
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/1146)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+
+<!-- tocstop -->
+
+## Problem
+
+Most aspects of generic parameterization are the same between functions and
+types, but there are a few things specific to types. In particular:
+
+-   the declaration of a type can contain a greater variety of members, and
+-   types have identity which affects type comparisons, deduced parameters, and
+    implied constraints.
+
+We also want a
+[generic specialization](/docs/design/generics/terminology.md#generic-specialization)
+story that works well for types, without giving up the ability to type check
+users of a type without knowing which specializations apply.
+
+## Background
+
+C++ supports specialization, including partial specialization, for templated
+types and functions.
+
+## Proposal
+
+This proposal adds a
+["parameterized types" section](/docs/design/generics/details.md#parameterized-types)
+to the [detailed design of generics](/docs/design/generics/details.md). Of note,
+it proposes not to support specialization of types or functions since those use
+cases can be handled by delegating to interfaces, which already support
+specialization.
+
+## Rationale based on Carbon's goals
+
+Specialization is important for allowing code to be generic without sacrificing
+[performance](/docs/project/goals.md#performance-critical-software). Since there
+is already a way to support specialization use cases without adding direct
+support for specializing types, this proposal follows the Carbon principle to
+[prefer providing only one way to do a given thing](/docs/project/principles/one_way.md).
+By avoiding another way of customizing behavior for specific types, it makes
+interfaces the
+[single static open extension mechanism](/docs/project/principles/static_open_extension.md).
+This proposal maintains consistency between generic parameterization of types
+and functions, in support of
+[code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
+
+## Alternatives considered
+
+We considered supporting specialization for types directly. To support type
+checking in a generic context, the API of the type needs to be defined
+independent of which specialization is selected. This would have introduced
+complexity into the language:
+
+-   Would the API of the type be represented by an `interface`?
+-   Would the type's API be explicitly declared or inferred from the type
+    declaration by some process, at the risk of including details that are not
+    necessarily stable?
+-   How would public data members be handled, since interfaces (currently) don't
+    support them?
+-   How would we support non-monomorphizing generic strategies? With the current
+    proposal, the layout of a parameterized type is known unless it uses an
+    associated type.
+
+The main disadvantage of the proposed approach is that the author of the type
+needs to define the ways that the type can be customized. We will need to see if
+this ends up being a problem in practice. It may turn out to be a benefit, by
+giving more information about the implementation of a class to readers.