Generics overview (#524)

This adds an overview of a generics feature that attempts to achieve the goals from #24 . It has been summarized in these presentations:

- [non-type params](https://docs.google.com/presentation/d/1IZaDxP5Y3Wqprkyjzagv48tyxEeIcfz8FZS3Namsvew/edit#slide=id.p)
- [basic usage](https://docs.google.com/presentation/d/1OZiMTVW2Ommop5WTs9RyEwnGxy9yzaAPF7Cj5KUfDsY/edit?resourcekey=0-Nya0Soz3ZNs3hJan8VIrTA#slide=id.p)
- [more advanced usage](https://docs.google.com/presentation/d/1bg6q0Q9Sk4YpRbNA3D3H34xYtaEO8ScAUNUZK2UTi80/edit?resourcekey=0-6-Y6e1mfRUmHg-Zk65Gc5A#slide=id.p)

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Co-authored-by: Wolff Dobson <wolffg@users.noreply.github.com>
This commit is contained in:
josh11b
2021-06-29 10:52:45 -07:00
committed by GitHub
co-authored by Jon Meow Chandler Carruth Wolff Dobson
parent 8082791c54
commit 978e27f484
4 changed files with 569 additions and 1 deletions
+3 -1
View File
@@ -9,7 +9,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
This directory contains the collection of documents describing the generics
feature of Carbon:
- ~~Overview~~ - not implemented yet
- [Overview](overview.md) - A high-level description of the generics design,
with pointers to other design documents that dive deeper into individual
topics.
- [Goals](goals.md) - The motivation and principles guiding the design
direction.
- [Terminology](terminology.md) - A glossary establishing common terminology
+506
View File
@@ -0,0 +1,506 @@
# Carbon generics overview
<!--
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
-->
This document is a high-level description of Carbon's generics design, with
pointers to other design documents that dive deeper into individual topics.
<!-- toc -->
## Table of contents
- [Goals](#goals)
- [Summary](#summary)
- [What are generics?](#what-are-generics)
- [Interfaces](#interfaces)
- [Defining interfaces](#defining-interfaces)
- [Contrast with templates](#contrast-with-templates)
- [Implementing interfaces](#implementing-interfaces)
- [Qualified and unqualified access](#qualified-and-unqualified-access)
- [Type-of-types](#type-of-types)
- [Generic functions](#generic-functions)
- [Deduced parameters](#deduced-parameters)
- [Generic type parameters](#generic-type-parameters)
- [Requiring or extending another interface](#requiring-or-extending-another-interface)
- [Combining interfaces](#combining-interfaces)
- [Structural interfaces](#structural-interfaces)
- [Type erasure](#type-erasure)
- [Future work](#future-work)
<!-- tocstop -->
## 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:
- Function calls and bodies are checked independently against the function
signatures.
- Clearer and earlier error messages.
- Fast builds, particularly development builds.
- Support for both static and dynamic dispatch.
For more detail, see [the detailed discussion of generics goals](goals.md) and
[generics terminology](terminology.md).
## Summary
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 items 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.
- Interfaces can require other interfaces be implemented, or
[extend](terminology.md#extendingrefining-an-interface) them.
- 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
["structural interfaces"](terminology.md#structural-interfaces). Structural
interfaces can express requirements that multiple interfaces be implemented,
and give you control over how name conflicts are handled.
- Alternatively, you may resolve name conflicts by using a qualified syntax to
directly call a function from a specific interface.
## What are generics?
Generics are a mechanism for writing parameterized code that applies generally
instead of making near-duplicates for very similar situations, much like C++
templates. For example, instead of having one function per type-you-can-sort:
```
fn SortInt32Vector(a: Vector(Int32)*) { ... }
fn SortStringVector(a: Vector(String)*) { ... }
...
```
You might have one generic function that could sort any array with comparable
elements:
```
fn SortVector(T:$ Comparable, a: Vector(T)*) { ... }
```
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`,
`SortVector(String, &sv)` is equivalent to `SortStringVector(&sv)`. Thus, we can
sort any vector containing comparable elements using this single `SortVector`
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
that the compiler can:
- 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.
In this example, then, `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
the `SortVector` function. This includes checking that the `Comparable`
requirement covers all of the uses of `T` inside the function.
Later, when the compiler comes across a call to `SortVector`, it can type check
against the requirements expressed in the function's signature. Using only the
types at the call site, the compiler can check that the member elements of the
passed-in array satisfy the function's requirements. There is no need to look at
the body of the `SortVector` function, since we separately checked that those
requirements were sufficient.
#### Defining interfaces
Interfaces, then, have a name and describe methods, functions, and other items
for types to implement.
Example:
```
interface Comparable {
// Placeholder method syntax
// `Less` is an associated method.
method (this: Self) Less(that: Self) -> Bool;
}
```
[Question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494)
is on method syntax.
Interfaces describe functionality, but not data; no variables may be declared in
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
[instantiations](terminology.md#instantiation) are known.
Note: The doc on [Generics terminology](terminology.md) goes into more detail
about the
[differences between generics and templates](terminology.md#generic-versus-template-parameters).
### Implementing interfaces
Interfaces themselves only describe functionality by way of method descriptions.
A type needs to _implement_ an interface to indicate that it supports its
functionality. A given type may implement an interface at most once.
Consider this interface:
```
interface Printable {
method (this: Self) Print();
}
```
The `interface` keyword is used to define a
[_nominal interface_](terminology.md#nominal-interfaces). That means that types
need to explicitly implement them, using an `impl` block, such as here:
```
struct Song {
// ...
// Implementing `Printable` for `Song` inside the definition of `Song`
// means all names of `Printable`, such as `F`, are included as a part
// of the `Song` API.
impl as Printable {
// Could use `Self` in place of `Song` here.
method (this: Song) Print() { ... }
}
}
// Implement `Comparable` for `Song` without changing the API of `Song`
// using an `external impl` declaration. This may be defined in either
// the library defining `Song` or `Comparable`.
external impl Song as Comparable {
// Could use either `Self` or `Song` here.
method (this: Self) Less(that: Self) -> Bool { ... }
}
```
**Note:** The interface implementation syntax was decided in
[question-for-leads issue #575](https://github.com/carbon-language/carbon-lang/issues/575).
TODO: move this 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.
#### Qualified and unqualified access
The methods of an interface implemented within the struct definition may be
called with the unqualified syntax. All methods of implemented interfaces may be
called with the qualified syntax, whether they are defined internally or
externally.
```
var song: Song;
// `song.Print()` is allowed, unlike `song.Play()`.
song.Print();
// `Less` is defined in `Comparable`, which is implemented
// externally for `Song`
song.(Comparable.Less)(song);
// Can also call `Print` using the qualified syntax:
song.(Printable.Print)();
```
### Type-of-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#type-constraints).
In general, a type-of-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 type is that it must implement the interface `Comparable`.
A type-of-type also defines a set of names and a mapping to corresponding
qualified names. You may combine interfaces into new type-of-types using
[the `&` operator](#combining-interfaces) or
[structural interfaces](#structural-interfaces).
### Generic functions
We want to be able to call generic functions just like ordinary functions, and
write generic function bodies like ordinary functions. There are only a few
differences, like that you can't take the address of generic functions.
#### Deduced parameters
This `SortVector` function is explicitly providing type information that is
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)*) { ... }
```
The `T` parameter is defined in square brackets before the explicit parameter
list in parenthesis to indicate it should be deduced. This means you may call
the function without the type argument, just like the ordinary functions
`SortInt32Vector` or `SortStringVector`:
```
SortVectorDeduced(&anIntVector);
// or
SortVectorDeduced(&aStringVector);
```
and the compiler deduces that the `T` argument should be set to `Int32` or
`String` from the type of the argument.
Deduced arguments are always determined from the call and its explicit
arguments. There is no syntax for specifying deduced arguments directly at the
call site.
```
// ERROR: can't determine `U` from explicit parameters
fn Illegal[Type:$ T, Type:$ U](x: T) -> U { ... }
```
#### Generic type parameters
A function with a generic type parameter can have the same function body as an
unparameterized one.
```
fn PrintIt[T:$ Printable](p: T*) {
p->Print();
}
fn PrintIt(p: Song*) {
p->Print();
}
```
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.
You may also refer to any of the methods of interfaces required by the
type-of-type using the [qualified syntax](#qualified-and-unqualified-access), as
shown in the following sections.
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._
### Requiring or extending another interface
Interfaces can require other interfaces be implemented:
```
interface Equatable {
method (this: Self) IsEqual(that: Self) -> Bool;
}
// `Iterable` requires that `Equatable` is implemented.
interface Iterable {
impl as Equatable;
method (this: Self*) Advance();
}
```
The `extends` keyword is used to
[extend](terminology.md#extendingrefining-an-interface) another interface. If
interface `Child` extends interface `Parent`, `Parent`'s interface is both
required and all its methods are included in `Child`'s interface.
```
// `Hashable` extends `Equatable`.
interface Hashable {
extends Equatable;
method (this: Self) Hash() -> UInt64;
}
// `Hashable` is equivalent to:
interface Hashable {
impl as Equatable;
alias IsEqual = Equatable.IsEqual;
method (this: Self) Hash() -> UInt64;
}
```
A type may implement the parent interface implicitly by implementing all the
methods in the child implementation.
```
struct Key {
// ...
impl as Hashable {
method (this: Key) IsEqual(that: Key) -> Bool { ... }
method (this: Key) Hash() -> UInt64 { ... }
}
// No need to separately implement `Equatable`.
}
var k: Key = ...;
k.Hash();
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.
```
interface Renderable {
method (this: Self) GetCenter() -> (Int, Int);
// Draw the object to the screen
method (this: Self) Draw();
}
interface EndOfGame {
method (this: Self*) SetWinner(player: Int);
// Indicate the game was a draw
method (this: Self*) Draw();
}
fn F[T:$ Renderable & EndOfGame](game_state: T*) -> (Int, Int) {
game_state->SetWinner(1);
return game_state->Center();
}
```
Names with conflicts can be accessed using the
[qualified syntax](#qualified-and-unqualified-access).
```
fn BothDraws[T:$ Renderable & EndOfGame](game_state: T*) {
game_state->(Renderable.Draw)();
game_state->(GameState.Draw)();
}
```
#### Structural interfaces
You may also declare a new type-of-type directly using
["structural interfaces"](terminology.md#structural-interfaces). Structural
interfaces can express requirements that multiple interfaces be implemented, and
give you control over how name conflicts are handled. Structural interfaces have
other applications and capabilities not covered here.
```
structural interface Combined {
impl as Renderable;
impl as EndOfGame;
alias Draw_Renderable = Renderable.Draw;
alias Draw_EndOfGame = EndOfGame.Draw;
alias SetWinner = EndOfGame.SetWinner;
}
fn CallItAll[T:$ Combined](game_state: T*, int winner) {
if (winner > 0) {
game_state->SetWinner(winner);
} else {
game_state->Draw_EndOfGame();
}
game_state->Draw_Renderable();
// Can still use qualified syntax for names
// not defined in the structural interface
return game_state->(Renderable.Center)();
}
```
#### 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.
For example: If there were a class `CDCover` defined this way:
```
struct CDCover {
impl as Printable {
...
}
}
```
it can be passed to this `PrintIt` function:
```
fn PrintIt[T:$ Printable](p: T*) {
p->Print();
}
```
At that point, two erasures occur:
- All of `CDCover`'s API _except_ `Printable` is erased during the cast from
`CDCover` to `Printable`, which is the [facet](terminology.md#facets) type
`CDCover as Printable`.
- When you call `PrintIt`, the type connection to `CDCover` is lost. Outside
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`.
## 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
parameters. When there are two implementations that can apply, there is a
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.
- 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
interfaces and other ways to reuse code across implementations.
- There should be a way to define generic associated and higher-ranked/kinded
types.
+1
View File
@@ -55,6 +55,7 @@ request:
- [0438 - Functions](p0438.md)
- [0444 - GitHub Discussions](p0444.md)
- [0447 - Generics terminology](p0447.md)
- [0524 - Generics overview](p0524.md)
- [0538 - `return` with no argument](p0538.md)
- [0555 - Operator precedence](p0555.md)
+59
View File
@@ -0,0 +1,59 @@
# Generics overview
<!--
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/524)
<!-- 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
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 a feature to land in a single proposal. This proposal specifies
the feature at the level of an overview description. This overview document is
intended to act as a starting point for people interested in the generics
feature by providing:
- a high-level description of the generics feature, and
- pointers to documents that go deeper into individual topics.
## Background
This is a follow on to these previous generics proposals:
- [Generics goals #24](https://github.com/carbon-language/carbon-lang/pull/24)
- [Generics terminology #447](https://github.com/carbon-language/carbon-lang/pull/447)
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
[this overview document](/docs/design/generics/overview.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
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).