Skip to content

Commit

Permalink
:! generic syntax (#676)
Browse files Browse the repository at this point in the history
This implements decision #565 to use `T:! Type` to declare generic parameters, and `template T:! Type` for template parameters.

Co-authored-by: Richard Smith <[email protected]>
  • Loading branch information
josh11b and zygoloid authored Aug 4, 2021
1 parent fac4343 commit 470ea2c
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 19 deletions.
28 changes: 9 additions & 19 deletions docs/design/generics/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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)();
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
}
```
Expand Down
1 change: 1 addition & 0 deletions proposals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 249 additions & 0 deletions proposals/p0676.md
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 470ea2c

Please sign in to comment.