Эх сурвалжийг харах

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>
josh11b 4 жил өмнө
parent
commit
978e27f484

+ 3 - 1
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

+ 506 - 0
docs/design/generics/overview.md

@@ -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 - 0
proposals/README.md

@@ -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 - 0
proposals/p0524.md

@@ -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).