diff --git a/docs/design/generics/README.md b/docs/design/generics/README.md index a927a57926158..c49f321938105 100644 --- a/docs/design/generics/README.md +++ b/docs/design/generics/README.md @@ -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 diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md new file mode 100644 index 0000000000000..2c251a918a57e --- /dev/null +++ b/docs/design/generics/overview.md @@ -0,0 +1,506 @@ +# Carbon generics overview + + + +This document is a high-level description of Carbon's generics design, with +pointers to other design documents that dive deeper into individual topics. + + + +## 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) + + + +## 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. diff --git a/proposals/README.md b/proposals/README.md index c4ee46c5db449..9ce04f92545bc 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -54,6 +54,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) diff --git a/proposals/p0524.md b/proposals/p0524.md new file mode 100644 index 0000000000000..492f72b4e5c31 --- /dev/null +++ b/proposals/p0524.md @@ -0,0 +1,59 @@ +# Generics overview + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/524) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + + + +## 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).