Przeglądaj źródła

`:!` generic syntax (#676)

This implements decision #565 to use `T:! Type` to declare generic parameters, and `template T:! Type` for template parameters.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b 4 lat temu
rodzic
commit
48b7dc3b81
3 zmienionych plików z 259 dodań i 19 usunięć
  1. 9 19
      docs/design/generics/overview.md
  2. 1 0
      proposals/README.md
  3. 249 0
      proposals/p0676.md

+ 9 - 19
docs/design/generics/overview.md

@@ -101,10 +101,10 @@ You might have one generic function that could sort any array with comparable
 elements:
 
 ```
-fn SortVector(T:$ Comparable, a: Vector(T)*) { ... }
+fn SortVector(T:! Comparable, a: Vector(T)*) { ... }
 ```
 
-The syntax above adds a `$` to indicate that the parameter named `T` is generic.
+The syntax above adds a `!` to indicate that the parameter named `T` is generic.
 
 Given an `Int32` vector `iv`, `SortVector(Int32, &iv)` is equivalent to
 `SortInt32Vector(&iv)`. Similarly for a `String` vector `sv`,
@@ -114,9 +114,6 @@ function.
 
 This ability to generalize makes `SortVector` a _generic_.
 
-**NOTE:** The `:$` syntax is a placeholder. The syntax is being decided in
-[question-for-leads issue #565](https://github.com/carbon-language/carbon-lang/issues/565).
-
 ### Interfaces
 
 The `SortVector` function requires a definition of `Comparable`, with the goal
@@ -155,9 +152,6 @@ interface Comparable {
 }
 ```
 
-**Note:** The method syntax was decided in
-[question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494).
-
 Interfaces describe functionality, but not data; no variables may be declared in
 an interface.
 
@@ -212,10 +206,6 @@ external impl Song as Comparable {
 }
 ```
 
-**Note:** The interface implementation syntax was decided in
-[question-for-leads issue #575](https://github.com/carbon-language/carbon-lang/issues/575).
-TODO: move these syntax issues to details and link.
-
 Implementations may be defined within the struct definition itself or
 externally. External implementations may be defined in the library defining the
 interface.
@@ -272,7 +262,7 @@ already included in the type of the second argument. To eliminate the argument
 at the call site, use a _deduced parameter_.
 
 ```
-fn SortVectorDeduced[T:$ Comparable](a: Vector(T)*) { ... }
+fn SortVectorDeduced[T:! Comparable](a: Vector(T)*) { ... }
 ```
 
 The `T` parameter is defined in square brackets before the explicit parameter
@@ -295,7 +285,7 @@ call site.
 
 ```
 // ERROR: can't determine `U` from explicit parameters
-fn Illegal[T:$ Type, U:$ Type](x: T) -> U { ... }
+fn Illegal[T:! Type, U:! Type](x: T) -> U { ... }
 ```
 
 #### Generic type parameters
@@ -304,7 +294,7 @@ A function with a generic type parameter can have the same function body as an
 unparameterized one.
 
 ```
-fn PrintIt[T:$ Printable](p: T*) {
+fn PrintIt[T:! Printable](p: T*) {
   p->Print();
 }
 
@@ -396,7 +386,7 @@ interface EndOfGame {
   fn Draw[addr me: Self*]();
 }
 
-fn F[T:$ Renderable & EndOfGame](game_state: T*) -> (Int, Int) {
+fn F[T:! Renderable & EndOfGame](game_state: T*) -> (Int, Int) {
   game_state->SetWinner(1);
   return game_state->Center();
 }
@@ -406,7 +396,7 @@ Names with conflicts can be accessed using the
 [qualified syntax](#qualified-and-unqualified-access).
 
 ```
-fn BothDraws[T:$ Renderable & EndOfGame](game_state: T*) {
+fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) {
   game_state->(Renderable.Draw)();
   game_state->(GameState.Draw)();
 }
@@ -429,7 +419,7 @@ structural interface Combined {
   alias SetWinner = EndOfGame.SetWinner;
 }
 
-fn CallItAll[T:$ Combined](game_state: T*, int winner) {
+fn CallItAll[T:! Combined](game_state: T*, int winner) {
   if (winner > 0) {
     game_state->SetWinner(winner);
   } else {
@@ -461,7 +451,7 @@ struct CDCover  {
 it can be passed to this `PrintIt` function:
 
 ```
-fn PrintIt[T:$ Printable](p: T*) {
+fn PrintIt[T:! Printable](p: T*) {
   p->Print();
 }
 ```

+ 1 - 0
proposals/README.md

@@ -63,6 +63,7 @@ request:
 -   [0618 - var ordering](p0618.md)
 -   [0623 - Require braces](p0623.md)
 -   [0646 - Low context-sensitivity principle](p0646.md)
+-   [0676 - `:!` generic syntax](p0676.md)
 -   [0680 - And, or, not](p0680.md)
 
 <!-- endproposals -->

+ 249 - 0
proposals/p0676.md

@@ -0,0 +1,249 @@
+# `:!` generic syntax
+
+<!--
+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/676)
+
+<!-- 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)
+    -   [Default based on context](#default-based-on-context)
+        -   [Square brackets](#square-brackets)
+    -   [Other spellings that were considered](#other-spellings-that-were-considered)
+    -   [`Template` as a type-of-type](#template-as-a-type-of-type)
+-   [Alternatives not considered](#alternatives-not-considered)
+
+<!-- tocstop -->
+
+## Problem
+
+Carbon design docs provisionally used `:$` to mark generic parameters. Since
+then, [issue #565](https://github.com/carbon-language/carbon-lang/issues/565)
+decided to use `:!` more permanently. This proposal is to implement that
+decision.
+
+## Background
+
+Most popular languages put generic parameters inside angle brackets (`<`...`>`),
+as can be seen on rosettacode.org:
+[1](http://rosettacode.org/wiki/Generic_swap),
+[2](http://rosettacode.org/wiki/Constrained_genericity).
+
+## Proposal
+
+Generic parameters will be marked using `:!` instead of `:` in the parameter
+list. They are listed with the regular parameters if they are to be specified
+explicitly by callers.
+
+```
+fn Zero(T:! ConvertFrom(Int)) -> T;
+
+var zf: Float32 = Zero(Float32);
+```
+
+If they are instead deduced from the (types of) the regular parameters, they are
+listed in square brackets (`[`...`]`) before the parameter list in round parens
+(`(`...`)`).
+
+```
+fn Swap[T:! Movable](a: T*, b: T*);
+
+var i: Int = 1;
+var j: Int = 2;
+Swap(&i, &j);
+```
+
+Template parameters use both a `template` keyword before the parameter and `:!`
+in place of `:`.
+
+```
+fn FieldNames(template T:! Type) -> String;
+
+Assert(FieldNames(struct {.x: Int, .y: Int}) == "x, y");
+```
+
+For both generic and template parameters, the `!` means "compile time." There is
+some precedent for this meaning; Rust uses `!` to mark macro calls, that is
+calls that happen at compile time.
+
+## Rationale based on Carbon's goals
+
+We are attempting to choose a syntax that advances Carbon's goal of having
+[code that is easy to read, understand, and write](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
+This option was chosen since it has the advantage of being very simple and not
+relying on any context, in accordance with the
+[#646: low-context-sensitivity principle](https://github.com/carbon-language/carbon-lang/pull/646).
+
+For ease of parsing, we've been trying to avoid using angle brackets (`<`...`>`)
+outside of declarations. This is primarily a concern for parameterized types
+that can appear in expressions alongside comparison operators (`<` and `>`).
+
+The choice to mark template parameters with a keyword was to make it very
+visible when that powerful and dangerous feature was being used. We also liked
+the similarities to how a `template` keyword also introduces a C++ template
+declaration.
+
+The choice to use the specific symbols `:!` over `:$` or other possibilities was
+just a matter of taste.
+
+## Alternatives considered
+
+There were a few other options considered to designate generics in
+[issue #565](https://github.com/carbon-language/carbon-lang/issues/565).
+
+Note that we at first considered the possibility that type parameters might
+accidentally be declared as dynamic if that was the default. We eventually
+decided that we could forbid dynamic type parameters for now, and revisit this
+problem if and when we decided to add a dynamic type parameter feature.
+
+### Default based on context
+
+In a given syntactic context, one option is going to be more common than others:
+
+-   Regular explicit parameters would most commonly be dynamic.
+-   Deduced parameters would most commonly be generic.
+-   Parameters to interfaces and types would most commonly be generic.
+
+We considered making `x: T` be generic or dynamic based on this context. In
+cases where this default was not what was intended, there would be a keyword
+(`dynamic`, `generic`, or `template`) to explicitly pick.
+
+There were a few variations about whether to treat parameters used in types
+differently. This had the downside of being harder to discern at a glance.
+
+The main benefits of this approach were:
+
+-   It handled dynamic type parameters being allowed but uncommon more
+    gracefully.
+-   Users could use `:` and it would generally do the right thing.
+-   Keywords are generally easier to find in search engines, and more
+    self-explanatory.
+-   Template parameters in particular were highlighted, a property shared with
+    the approach recommended by this proposal.
+
+The main objections to this approach was that it was context-sensitive and there
+was a lack of syntactic consistency in the context. That is, the were two kinds
+of context, generic and dynamic, and two kinds of brackets, square and parens,
+but sometimes the parens would be generic and sometimes not.
+
+#### Square brackets
+
+Using `[`...`]` for generics creates the opposite problem of the brackets being
+inconsistent with the deduced or explicit distinction.
+
+```
+// `T` is an explicit generic parameter to `Vector`
+class Vector[T: Type] { ... }
+// `T` and `DestT` are generic parameters, with `T` deduced and
+// `DestT` explicit.
+fn CastAVector[T: Type](v: Vector[T], generic DestT: Type) -> Vector[DestT];
+```
+
+### Other spellings that were considered
+
+There were a number of other options considered. None of them were compelling,
+though this mostly came down to taste.
+
+```
+class Vector(<T: Type>) { ... }
+fn CastAVector<T: Type>(v: Vector(T), <DestT: CastFrom(T)>) -> Vector(DestT);
+var from: Vector(i32) = ...;
+var to: Vector(i64) = CastAVector(from, i64);
+```
+
+-   not trivial to parse, but doable
+-   too much punctuation
+-   nice that `<`...`>` is associated with generics, but with enough differences
+    to be concerning
+
+```
+fn CastAVector[T: Type](v: Vector(T), [DestT: Type]) -> Vector(DestT);
+class Vector([T: Type]) { ... }
+var from: Vector(i32) = ...;
+var to: Vector(i64) = CastAVector(from, i64);
+```
+
+-   There was no reason to prefer this over the previous option, since `<`...`>`
+    is more associated with generics than `[`...`]`.
+
+Other different spellings of the `:!` position that came up during brainstroming
+but were not found to be compelling included:
+
+-   `<id>: Type`
+-   `id:# Type`
+-   `id:<> Type`
+-   `id: <Type>`
+-   `generic id: Type`
+
+### `Template` as a type-of-type
+
+We talked about the alternative of using a keyword like `Auto` or `Template` as
+a type-of-type to indicate that a parameter was a template.
+
+```
+fn FieldNames(T: Template) -> String;
+```
+
+This would be able to be combined with other type-of-types using the `&`
+operator to constrain the allowed types, as in `Container & Template`. The idea
+is that `Auto` or `Template` would act like an interface that contained any
+calls used by the function that were not in any other interface constraint for
+that parameter.
+
+It had two downsides:
+
+-   This approach only worked for type parameters. We would need something else
+    for non-type template parameters.
+-   It didn't seem like you would want to be able to hide an `& Auto` or
+    `& Template` clause by declaring it in a constant:
+
+    ```
+    let CT = Container & Template;
+    fn SurpriseIHaveATemplateParam[T: CT](c: T);
+    ```
+
+That suggests that the `Auto` or `Template` keyword would not act like other
+type-of-type expressions and would need special treatment.
+
+## Alternatives not considered
+
+We never really broke out of the idea that `[`...`]` were for deduced
+parameters. As a result we didn't really consider options where type expressions
+used square brackets for generic parameters, as in `Vector[Int]`, even though
+that addresses the parsing problems of angle brackets. For example, if we were
+to revisit this decision, we might use three kinds of brackets for the three
+different cases:
+
+-   `<`...`>` for deduced and generic parameters
+-   `[`...`]` for explicit and generic parameters
+-   `(`...`)` for explicit and dynamic parameters
+
+Code would most commonly use square brackets both when declaring and using a
+parameterized type or an interface. Parens would be required for functions, with
+generic parameters typically specified using angle brackets.
+
+```
+class Vector[T: Type] { ... }
+fn CastAVector<T: Type>[DestT: CastFrom[T]](v: Vector[T]) -> Vector[DestT];
+var from: Vector[i32] = ...;
+var to: Vector[i64] = CastAVector[i64](from);
+```
+
+One concern is that use of square brackets will be in the same contexts as
+`[`...`]` will be used for indexing. Another concern is that there is
+[some motivation](http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p1045r1.html)
+for putting generic and template parameters together with regular parameters in
+the `(`...`)` parameter list
+
+[Go ultimately did decided to use square brackets for generics](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md).
+This is even though Go also uses square brackets for slices and indexing.