Преглед изворни кода

Constraints for generics (generics details 3) (#818)

This proposal describes `where` clauses that can add constraints on a type-of-type, for example define restrictions on its associated types. Example:

```
fn FindFirstPrime[T:! Container where .Element = i32]
    (c: T) -> Optional(i32) {
  // The elements of `c` have type `T.Element`, which is `i32`.
  ...
}

fn PrintContainer[T:! Container where .Element is Printable](c: T) {
  // The type of the elements of `c` is not known, but we do know
  // that type satisfies the `Printable` interface.
  ...
}
```

Some other constraints, such as `Sized` are defined as type-of-types directly, possibly parameterized.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
josh11b пре 4 година
родитељ
комит
89a829b8c7

Разлика између датотеке није приказан због своје велике величине
+ 1210 - 58
docs/design/generics/details.md


+ 2 - 1
docs/design/generics/goals.md

@@ -532,7 +532,8 @@ are complicated and
 
 ### Interfaces are nominal
 
-Interfaces can either be structural, as in Go, or nominal, as in Rust and Swift.
+Interfaces can either be [structural](terminology.md#structural-interfaces), as
+in Go, or [nominal](terminology.md#nominal-interfaces), as in Rust and Swift.
 Structural interfaces match any type that has the required methods, whereas
 nominal interfaces only match if there is an explicit declaration stating that
 the interface is implemented for that specific type. Carbon will support nominal

+ 36 - 13
docs/design/generics/overview.md

@@ -27,12 +27,13 @@ pointers to other design documents that dive deeper into individual topics.
         -   [Generic type parameters](#generic-type-parameters)
     -   [Requiring or extending another interface](#requiring-or-extending-another-interface)
     -   [Combining interfaces](#combining-interfaces)
-        -   [Structural interfaces](#structural-interfaces)
+        -   [Named constraints](#named-constraints)
         -   [Type erasure](#type-erasure)
     -   [Adapting types](#adapting-types)
     -   [Interface input and output types](#interface-input-and-output-types)
         -   [Associated types](#associated-types)
         -   [Parameterized interfaces](#parameterized-interfaces)
+    -   [Constraints](#constraints)
 -   [Future work](#future-work)
 -   [References](#references)
 
@@ -84,9 +85,9 @@ Summary of how Carbon generics work:
 -   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.
+    ["named constraints"](terminology.md#named-constraints). Named constraints
+    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.
 
@@ -251,7 +252,7 @@ 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).
+[named constraints](#named-constraints).
 
 ### Generic functions
 
@@ -406,16 +407,16 @@ fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) {
 }
 ```
 
-#### Structural interfaces
+#### Named constraints
 
 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.
+["named constraints"](terminology.md#named-constraints). Named constraints can
+express requirements that multiple interfaces be implemented, and give you
+control over how name conflicts are handled. Named constraints have other
+applications and capabilities not covered here.
 
 ```
-structural interface Combined {
+constraint Combined {
   impl as Renderable;
   impl as EndOfGame;
   alias Draw_Renderable = Renderable.Draw;
@@ -431,7 +432,7 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) {
   }
   game_state->Draw_Renderable();
   // Can still use qualified syntax for names
-  // not defined in the structural interface
+  // not defined in the named constraint
   return game_state->(Renderable.Center)();
 }
 ```
@@ -561,9 +562,30 @@ fn FindInVector[T:! Type, U:! Equatable(T)](v: Vector(T), needle: U)
 fn CompileError[T:! Type, U:! Equatable(T)](x: U) -> T;
 ```
 
+### Constraints
+
+Type-of-types can be further constrained using a `where` clause:
+
+```
+fn FindFirstPrime[T:! Container where .Element == i32]
+    (c: T, i: i32) -> Optional(i32) {
+  // The elements of `c` have type `T.Element`, which is `i32`.
+  ...
+}
+
+fn PrintContainer[T:! Container where .Element is Printable](c: T) {
+  // The type of the elements of `c` is not known, but we do know
+  // that type satisfies the `Printable` interface.
+  ...
+}
+```
+
+Constraints limit the types that the generic function can operate on, but
+increase the knowledge that may be used in the body of the function to operate
+on values of those types.
+
 ## Future work
 
--   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
@@ -583,3 +605,4 @@ fn CompileError[T:! Type, U:! Equatable(T)](x: U) -> T;
 
 -   [#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524)
 -   [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
+-   [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)

+ 13 - 2
docs/design/generics/terminology.md

@@ -24,6 +24,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Interface](#interface)
     -   [Structural interfaces](#structural-interfaces)
     -   [Nominal interfaces](#nominal-interfaces)
+    -   [Named constraints](#named-constraints)
 -   [Associated entity](#associated-entity)
 -   [Impls: Implementations of interfaces](#impls-implementations-of-interfaces)
 -   [Compatible types](#compatible-types)
@@ -298,6 +299,14 @@ We use the "structural" versus "nominal" terminology as a generalization of the
 same terms being used in a
 [subtyping context](https://en.wikipedia.org/wiki/Subtyping#Subtyping_schemes).
 
+### Named constraints
+
+Named constraints are "structural" in the sense that they match a type based on
+meeting some criteria rather than an explicit statement in the type's
+definition. The criteria for a named constraint, however, are less focused on
+the type's API and instead might include a set of nominal interfaces that the
+type must implement.
+
 ## Associated entity
 
 An _associated entity_ is a requirement in an interface that a type's
@@ -320,8 +329,10 @@ instead of associated entity.
 An _impl_ is an implementation of an interface for a specific type. It is the
 place where the function bodies are defined, values for associated types, etc.
 are given. Impls are needed for [nominal interfaces](#nominal-interfaces);
-[structural interfaces](#structural-interfaces) define conformance implicitly
-instead of by requiring an impl to be defined.
+[structural interfaces](#structural-interfaces) and
+[named constraints](#named-constraints) define conformance implicitly instead of
+by requiring an impl to be defined. In can still make sense to implement a named
+constraint as a way to implement all of the interfaces it requires.
 
 ## Compatible types
 

+ 3 - 0
docs/design/lexical_conventions/words.md

@@ -43,6 +43,7 @@ The following words are interpreted as keywords:
 -   `break`
 -   `case`
 -   `class`
+-   `constraint`
 -   `continue`
 -   `default`
 -   `else`
@@ -55,11 +56,13 @@ The following words are interpreted as keywords:
 -   `impl`
 -   `import`
 -   `interface`
+-   `is`
 -   `let`
 -   `library`
 -   `match`
 -   `namespace`
 -   `not`
+-   `observe`
 -   `or`
 -   `override`
 -   `package`

+ 535 - 0
proposals/p0818.md

@@ -0,0 +1,535 @@
+# Constraints for generics (generics details 3)
+
+<!--
+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/818)
+
+<!-- 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)
+    -   [Alternatives to `where` clauses modifying type-of-types](#alternatives-to-where-clauses-modifying-type-of-types)
+    -   [Different keyword than `where`](#different-keyword-than-where)
+    -   [Inline constraints instead of `.Self`](#inline-constraints-instead-of-self)
+    -   [Self reference instead of `.Self`](#self-reference-instead-of-self)
+    -   [Implied constraints across declarations](#implied-constraints-across-declarations)
+    -   [No implied constraints](#no-implied-constraints)
+    -   [Type inequality constraints](#type-inequality-constraints)
+    -   [`where .Self is ...` could act like an external impl](#where-self-is--could-act-like-an-external-impl)
+        -   [Other syntax for external impl](#other-syntax-for-external-impl)
+    -   [Other syntax for must be legal type constraints](#other-syntax-for-must-be-legal-type-constraints)
+    -   [Using only `==` instead of also `=`](#using-only--instead-of-also-)
+    -   [Automatic type equality](#automatic-type-equality)
+        -   [Restricted equality constraints](#restricted-equality-constraints)
+        -   [No explicit restrictions](#no-explicit-restrictions)
+
+<!-- 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 to land in a single proposal. This proposal continues
+[#553](https://github.com/carbon-language/carbon-lang/pull/553) defining the
+details of:
+
+-   adapters
+-   associated types and other constants
+-   parameterized interfaces
+
+## Background
+
+This is a follow on to these previous generics proposals:
+
+-   [#24: Generics goals](https://github.com/carbon-language/carbon-lang/pull/24)
+-   [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447)
+-   [#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524)
+-   [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
+-   [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
+
+Some of 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 two sections to
+[this design document on generics details](/docs/design/generics/details.md).
+
+## Rationale based on Carbon's goals
+
+Much of this rationale for generics was captured in the
+[Generics goals proposal](https://github.com/carbon-language/carbon-lang/pull/24).
+The specific decisions for this constraint design were motivated by:
+
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
+    -   The manual generic type equality approach being proposed could be
+        extended to automatically observe more types as being equal without
+        breaking existing code.
+    -   Adding or growing an `observe` statement has been designed to be
+        non-breaking since they can't introduce name conflicts, only make more
+        code legal.
+    -   This constraint system is expressive and does not have artificial limits
+        that would be needed to support automatic type equality.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   Attaching `where` clauses to type-of-types addresses more use cases than
+        attaching them to declarations, allowing the design to be smaller, see
+        [#780](https://github.com/carbon-language/carbon-lang/issues/780).
+    -   The manual generic type equality approach is very simple and easy to
+        understand how it works. It unfortunately is a bit more verbose, leading
+        to more to read and write.
+-   [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development)
+    -   The manual generic type equality approach does much less work at compile
+        time than the automatic approach. The extra work would only be done to
+        produce an error message that includes the changes to the source needed
+        to avoid repeating that work. In effect, the source code holds a cache
+        of the facts needed to compile it.
+
+## Alternatives considered
+
+### Alternatives to `where` clauses modifying type-of-types
+
+Issue
+[#780: How to write constraints](https://github.com/carbon-language/carbon-lang/issues/780)
+considered other forms that constraints could be written.
+
+One alternative is to place `where` clauses on declarations instead of types.
+
+```
+fn F[W:! Type, V:! D & E](v:V) -> W
+    where V.X == W, V.Y == W, V.Z = i32;
+```
+
+The constraints are written after the names of types are complete, and so
+constraints are naturally written in terms of those names. This is a common
+approach used by several languages including Swift and Rust, and so is likely
+familiar to a significant fraction of our users. The downside of this approach
+is it doesn't let you put constraints on types that never get a names, such as
+in
+["protocols as types" or "existential types" in Swift](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID275)
+and "trait objects" in Rust
+([1](https://doc.rust-lang.org/book/ch17-02-trait-objects.html),
+[2](https://doc.rust-lang.org/reference/types/trait-object.html)). This is a
+problem in practice, since those are cases where constraints are needed, since
+for type safety any associated types need to be constrained to be a specific
+concrete type. This motivated Rust to add another syntax just for specifying
+specific concrete types for associated types in the argument passing style. It
+is also motivation for Swift to consider a feature they call
+["generalized existentials"](https://github.com/apple/swift/blob/main/docs/GenericsManifesto.md#generalized-existentials),
+that is very similar to this proposal.
+
+Another alternative is to generalize the argument passing approach Rust allows
+to handle more kinds of constraints.
+
+```
+fn F[W:! Type, V:! D{.X = W} & E{.Y = W, .Z = i32}](v: V) -> W;
+```
+
+This would treat constraints on interface parameters and associated types in a
+more uniform way, but doesn't handle the full breadth of constraints we would
+like to express. We at some point believed that this framing of the constraints
+made it harder to express undecidable type equality problems and easier to
+enforce restrictions that would make the constraints decidable, but we
+eventually discovered that this formulation had the essentially the same
+difficulties.
+
+We considered a variation of argument passing that we called "whole expression
+constraint intersections."
+
+```
+fn F[W:! Type, V:! D & E & {.X = W, .Y = W, .Z = i32}](v: V) -> W;
+```
+
+This variation made it easy to set associated types from two interfaces with the
+same name to the same value, but introduced concerns that it would stop `&` from
+being associative and commutative. It was not chosen because it had the same
+downsides as the argument passing approach.
+
+### Different keyword than `where`
+
+We considered other keywords for introducing constraints, such as:
+
+-   `requires`, like
+    [C++](https://en.cppreference.com/w/cpp/language/constraints)
+-   `with`
+-   `if`, like [D](http://rosettacode.org/wiki/Constrained_genericity#D)
+-   `when`, like [F#](http://rosettacode.org/wiki/Constrained_genericity#F.23).
+
+The most common choice across popular languages is `where`, including:
+
+-   [C#](http://rosettacode.org/wiki/Constrained_genericity#C.23)
+-   [Haskell](http://rosettacode.org/wiki/Constrained_genericity#Haskell)
+-   [Rust](https://doc.rust-lang.org/rust-by-example/generics/where.html)
+-   Swift
+    ([1](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553),
+    [2](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID557))
+
+While C++ is particularly important to our target userbase, Carbon's constraints
+work more similarly to languages with generics like Rust and Swift.
+
+### Inline constraints instead of `.Self`
+
+We considered the possibility of using [named constraints](#named-constraints)
+instead of `.Self` for
+[recursive constraints](/docs/design/generics/details.md#recursive-constraints).
+This comes under consideration since `.Self` outside the named constraint is the
+same as `Self` inside. However, you can't always avoid using `.Self`, since
+naming the constraint before using it doesn't allow you to define this
+`Container` interface:
+
+```
+interface Container {
+  ...
+  let SliceType:! Container where .SliceType == .Self;
+  ...
+}
+```
+
+The problem that arises is to avoid using `.Self`, we would need to define the
+named constraint using `Container` before `Container` is defined:
+
+```
+constraint ContainerIsSlice {
+  // Error: forward reference
+  extends Container where Container.SliceType == Self;
+}
+
+interface Container {
+  ...
+  let SliceType:! ContainerIsSlice;
+  ...
+}
+```
+
+To work around this problem, we could allow the named constraint to be defined
+inline in the `Container` definition:
+
+```
+interface Container {
+  ...
+  constraint ContainerIsSlice {
+    extends Container where Container.SliceType == Self;
+  }
+  let SliceType:! ContainerIsSlice;
+  ...
+}
+```
+
+This alternative seems too cumbersome.
+
+### Self reference instead of `.Self`
+
+Another alternative instead of using `.Self` for
+[recursive constraints](/docs/design/generics/details.md#recursive-constraints),
+is to use the name of the type being declared inside the type declaration, as in
+`T:! HasAbs(.MagnitudeType = T)`. This had two downsides:
+
+-   Using the name of the type before it is finished being defined created
+    questions about what that name meant. Using the reserved token sequence
+    `.Self` instead makes it clearer that it obeys different rules than other
+    identifier references. For example, that we don't allow members to be
+    accessed.
+-   It doesn't address use cases where we are defining a type-of-type that isn't
+    associated with a type that has a name.
+
+### Implied constraints across declarations
+
+If constraints on an associated type could be implied by any declaration in the
+interface, readers and the type checker would be required to scan the entire
+interface definition and perform many type declaration lookups to understand the
+constraints on that associated type. This is particularly important when those
+constraints can be obscured in recursive references to the same interface:
+
+```
+interface I {
+  let A:! Type;
+  let B:! Type;
+  let C:! Type;
+  let D:! Type;
+  let E:! Type;
+  let SwapType:! I where .A == B and .B == A and .C == C
+                     and .D == D and .E == E;
+  let CycleType:! I where .A == B and .B == C and .C == D
+                      and .D == E and .E ==  A;
+  fn LookUp(hm: HashMap(D, E)*) -> E;
+  fn Foo(x: Bar(A, B));
+}
+```
+
+This applies equally to parameters:
+
+```
+interface I(A:! Type, B:! Type, C:! Type, D:! Type, E:! Type) {
+  let SwapType:! I(B, A, C, D, E);
+  let CycleType:! I(B, C, D, E, A);
+  fn LookUp(hm: HashMap(D, E)*) -> E;
+  fn Foo(x: Bar(A, B));
+}
+```
+
+All of the type arguments to `I` must actually implement `Hashable`, since
+[an adjacent swap and a cycle generate the full symmetry group on 5 elements](https://www.mathcounterexamples.net/generating-the-symmetric-group-with-a-transposition-and-a-maximal-length-cycle/)).
+And additional restrictions on those types would depend on the definition of
+`Bar`. For example, this definition
+
+```
+class Bar(A:! Type, B:! ComparableWith(A)) { ... }
+```
+
+would imply that all the type arguments to `I` would have to be comparable with
+each other. This propagation problem means that allowing constraints to be
+implied in this context is substantial, and potentially unbounded, work for the
+compiler and human readers.
+
+From this we conclude that we need the initial declaration part of an
+`interface`, type definition, or associated type declaration to include a
+complete description of all needed constraints.
+
+It is possible that this reasoning will apply more generally, but we will wait
+until we implement type checking to see what restrictions we actually need. For
+example, we might need to restate "parameterized type implements interface"
+constraints when it is on an associated type in a referenced interface, as in:
+
+```
+interface HasConstraint {
+  let T:! Type where Vector(.Self) is Printable;
+}
+
+interface RestatesConstraint {
+  // This works, since it restates the constraint on
+  // `HasConstraint.T` that `U` is equal to.
+  let U:! Type where Vector(.Self) is Printable;
+  // This doesn't work:
+  // ❌ let U:! Type;
+
+  let V:! HasConstraint where .T == U;
+}
+```
+
+### No implied constraints
+
+Issue [#809](https://github.com/carbon-language/carbon-lang/issues/809)
+considered whether Carbon would support implied constraints. The conclusion was
+that implied constraints was an important ergonomic improvement. The framing as
+a rewrite to a `where` restriction that did not affect the generic type
+parameter's unqualified API seemed like something that could be explained to
+users. Potential problems where a specialization of a generic type might allow
+that type to be instantiated for types that don't satisfy the normal constraints
+will be considered in the future along with the specialization feature.
+
+We also considered the alternative where the user would need to explicitly opt
+in to this behavior by adding `& auto` or `& implied_requirements` to their type
+constraint, as in:
+
+```
+fn LookUp[KeyType:! Type & auto](hm: HashMap(KeyType, i32)*,
+                                 k: KeyType) -> i32;
+
+fn PrintValueOrDefault[KeyType:! Printable & auto,
+                       ValueT:! Printable & HasDefault]
+    (map: HashMap(KeyType, ValueT), key: KeyT);
+```
+
+The feeling was that implied constraints were natural enough that they didn't
+need to be signaled by additional marking, with the accompanying ergonomic and
+brevity loss.
+
+### Type inequality constraints
+
+You might want an inequality type constraint, for example, to control overload
+resolution:
+
+```
+fn F[T:! Type](x: T) -> T { return x; }
+fn F(x: Bool) -> String {
+  if (x) return "True"; else return "False";
+}
+
+fn G[T:! Type where .Self != Bool](x: T) -> T {
+  // We need T != Bool for this to type check.
+  return F(x);
+}
+```
+
+There are some problems with supporting this feature, however.
+[Negative reasoning in general has been a source of difficulties in the implementation of Rust's type system](http://aturon.github.io/tech/2017/04/24/negative-chalk/).
+This is a type of constraint that is more difficult to use since it would have
+to be repeated by any generic caller, since nothing else can generically
+establish two types are different.
+
+Our manual type equality approach will not be able to detect situations where
+the two sides of the inequality in fact have to be equal due to a sequence of
+equality constraints. In those situations, no type will be ever be able to
+satisfy the inequality constraint.
+
+We may be able to overcome these difficulties, but we don't expect this feature
+is required since neither Rust nor Swift support it.
+
+### `where .Self is ...` could act like an external impl
+
+We considered making `T:! A where .Self is B` mean something different than
+`T:! A & B`. In particular, in the former case we considered whether `B` might
+be considered to be implemented externally for `T`. The advantage of this
+alternative is it gives a convenient way of not polluting the members of `T`
+with the members of `B`, however it wasn't clear how common that use case would
+be. Furthermore, it introduced an inconsistency with other associated types. For
+example:
+
+-   `T:! Container where .ElementType = i32` means that `T.ElementType` has type
+    `i32`.
+-   Following that, given `T:! Container where .ElementType is Printable` and
+    `x: T`, we expect that `x.Front().Print()` to be legal.
+-   To be consistent with the last point, it seems like given
+    `T:! Container where .Self is Printable` and `x: T`, then `x.Print()` should
+    be legal as well.
+
+This matches Rust, where `T: Container` is considered a synonym for
+`T where T is Container`.
+
+[See the Discord discussion](https://discord.com/channels/655572317891461132/708431657849585705/895794334723637258).
+
+#### Other syntax for external impl
+
+In a
+[follow up Discord discussion](https://discord.com/channels/655572317891461132/708431657849585705/902728293080530954),
+we considered allowing developers to write `where .Foo as Bar is Type` to mean
+"`.Foo` implements interface `Bar` externally." We would consider this feature
+in the future once we saw a demonstrated need.
+
+### Other syntax for must be legal type constraints
+
+Instead of `where HashSet(.Self) is Type` to say `.Self` must satisfy the
+constraints on being an argument to `HashSet`, we considered other syntaxes like
+`where HashSet(.Self) legal` and `where HashSet(.Self)`. The current choice is
+intended to be consistent with the syntax for "Parameterized type implements
+interface" and how implied constraints ensure that parameters to types
+automatically are given their needed constraints.
+
+### Using only `==` instead of also `=`
+
+Originally this proposal only used `==` for both setting an associated type to a
+specific concrete type and saying it was equal to another type variable. The two
+cases affected the type differently. In the specific concrete type case, the
+original type-of-type for the associated type doesn't affect the API, you just
+get the API of the type. When equating two type variables, though, the
+unqualified member names are unaffected. The only change was some interfaces may
+be implemented externally.
+
+In
+[this Discord discussion](https://discord.com/channels/655572317891461132/708431657849585705/902713789735116821),
+we decided it might be clearer to use two different operators to reflect the
+different effect. The compiler or a tool can easily correct cases where the
+developer used the wrong one. However, using `=` comes with the downside that it
+doesn't follow the pattern of other constraints of looking like a boolean
+expression returning `true` when the constraint is satisfied. We would consider
+a different pair of operators here to address this point, perhaps `~` for type
+variables and `==` for concrete type case.
+
+### Automatic type equality
+
+Other languages, such as Swift and Rust, don't require `observe` declarations or
+casting for the compiler to recognize two types as transitively equal. The
+problem is that there is know way to know how many equality constraints need to
+be considered before being able to conclude whether two type expressions are
+equal. In fact, the equality relations form a semi-group, where in general
+deciding whether two sequences are equivalent is undecidable, see
+[Swift type checking is undecidable - Discussion - Swift Forums](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024).
+
+#### Restricted equality constraints
+
+One possible approach to this problem is to apply limitations to the equality
+constraints developers are allowed to express. The goal is to identify some
+restrictions such that:
+
+-   We have a terminating algorithm to decide if a set of constraints meets the
+    restrictions.
+-   For constraints that meet the restrictions we have a terminating algorithm
+    for deciding which expressions are equal.
+-   Expected use cases satisfy the restrictions.
+
+The expected cases are things of these forms:
+
+-   `X == Y`
+-   `X == Y.Z`
+-   `X == X.Y`
+-   `X == Y.X`
+-   and _some_ combinations and variations
+
+For example, here are some interfaces that have been translated to Carbon syntax
+from Swift standard library protocols:
+
+```
+interface IteratorProtocol {
+  let Element:! Type;
+}
+
+interface Sequence {
+  let Element:! Type;
+  let Iterator:! IteratorProtocol
+    where .Element == Element;
+}
+
+interface Collection {
+  extends Sequence;
+  let Index:! Type;
+  let SubSequence:! Collection
+    where .Element == Element
+      and .SubSequence == SubSequence
+      and .Index == Index;
+  let Indices:! Collection
+    where .Element == Index
+      and .Index == Index
+      and .SubSequence == Indices;
+}
+```
+
+One approach we considered is
+[regular equivalence classes](p0818/regular_equivalence_classes.md), however we
+have not yet been able to figure out how to ensure the algorithm terminates.
+This is an approach we would like to reconsider if we find solutions to this
+problem once can share this problem more widely.
+
+Other approaches we considered worked in simple cases but had requirements that
+could not be validated, since for example they were equivalent to solving the
+same word problem that makes transitive equality undecidable in general.
+
+#### No explicit restrictions
+
+If you allow the full undecidable set of `where` clauses, there are some
+unpleasant options:
+
+-   Possibly the compiler won't realize that two expressions are equal even
+    though they should be.
+-   Possibly the compiler will reject some interface declarations as "too
+    complicated", due to "running for too many steps", based on some arbitrary
+    threshold.
+
+Either way, the compiler will have to perform a lot more work at compile time,
+slowing down builds. There is also a danger that composition of things that
+separately work or incremental evolution of working code could end up over a
+complexity or search depth threshold. In effect, there are still restrictions on
+what combinations of equality constraints are allowed, it is just that those
+restrictions are implicit in the specifics of the algorithm chosen and execution
+limits used.
+
+One approach that has been suggested by
+[Slava Pestov](https://forums.swift.org/u/Slava_Pestov) in the context of Swift,
+is to
+[formalize type equality as a term rewriting system](https://forums.swift.org/t/formalizing-swift-generics-as-a-term-rewriting-system/45175).
+Then the equality constraints in an interface or function declaration can be
+completed by running the Knuth-Bendix completion algorithm
+([1](https://en.wikipedia.org/wiki/Knuth%E2%80%93Bendix_completion_algorithm#Description_of_the_algorithm_for_finitely_presented_monoids),
+[2](https://academic.oup.com/comjnl/article/34/1/2/427931)) for some limited
+number of steps. If the algorithm completes successfully, type expressions may
+then be efficiently canonicalized. However the algorithm can fail, or fail to
+terminate before hitting the threshold number of steps, in which case the source
+code would have to be rejected. See
+[this example implementation](https://gist.github.com/slavapestov/75dbec34f9eba5fb4a4a00b1ee520d0b).

+ 226 - 0
proposals/p0818/regular_equivalence_classes.md

@@ -0,0 +1,226 @@
+# Regular equivalence classes
+
+<!--
+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
+-->
+
+## Problem
+
+For generics, users will define _interfaces_ that describe types. These
+interfaces may have associated types (see
+[Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID189),
+[Rust](https://doc.rust-lang.org/rust-by-example/generics/assoc_items/types.html)):
+
+```
+interface Container {
+  let Elt:! Type;
+  let Slice:! Container;
+  let Subseq:! Container;
+}
+```
+
+The next step is to express constraints between these associated types.
+
+-   `Container.Slice.Elt == Container.Elt`
+-   `Container.Slice.Slice == Container.Slice`
+-   `Container.Subseq.Elt == Container.Elt`
+-   `Container.Subseq.Slice == Container.Slice`
+-   `Container.Subseq.Subseq == Container.Subseq`
+
+Languages commonly express these constraints using `where` clauses, as in
+[Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID557) and
+[Rust](https://doc.rust-lang.org/rust-by-example/generics/where.html), that
+directly represent these constraints as equations.
+
+```
+interface Container {
+  let Elt:! Type;
+  let Slice:! Container where .Elt == Elt
+                          and .Slice == Slice;
+  let Subseq:! Container where .Elt == Elt
+                           and .Slice == Slice
+                           and .Subseq == Subseq;
+}
+```
+
+These relations give us a semi-group, where in general deciding whether two
+sequences are equivalent is undecidable, see
+[Swift type checking is undecidable - Discussion - Swift Forums](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024).
+
+If you allow the full undecidable set of `where` clauses, there are some
+unpleasant options:
+
+-   Possibly the compiler won't realize that two expressions are equal even
+    though they should be.
+-   Possibly the compiler will reject some interface declarations as "too
+    complicated", due to "running for too many steps", based on some arbitrary
+    threshold.
+
+Furthermore, the algorithms are expensive and perform extensive searches.
+
+### Goal
+
+The goal is to identify some restrictions such that:
+
+-   We have a terminating algorithm to decide if a set of constraints meets the
+    restrictions.
+-   For constraints that meet the restrictions we have a terminating algorithm
+    for deciding which expressions are equal.
+-   Expected use cases satisfy the restrictions.
+
+The expected cases are things of these forms:
+
+-   `X == Y`
+-   `X == Y.Z`
+-   `X == X.Y`
+-   `X == Y.X`
+-   and _some_ combinations and variations
+
+For ease of exposition, I'm going to assume that all associated types are
+represented by single letters, and omit the dots `.` between them.
+
+## Idea
+
+Observe that the equivalence class of words you can rewrite to in these expected
+cases are described by a regular expression:
+
+-   `X` can be rewritten to `(X|Y)` using the rewrite `X == Y`
+-   `X` can be rewritten to `XY*` using the rewrite `X == XY`
+-   `X` can be rewritten to `Y*X` using the rewrite `X == YX`
+
+These regular expressions can be used instead of the rewrite rules: anything
+matching the regular expression can be rewritten to anything else matched by the
+same regular expression. This means we can take any sequence of letters, replace
+anything that matches the regular expression by the regular expression itself,
+and get a regular expression that matches the original sequence of letters. We
+can even get a shortest/smallest representative of the class by dropping all the
+things with a `*` and picking the shortest/smallest between alternatives
+separated by a `|`.
+
+### Question
+
+Given a set of rewrite equations, is there a terminating algorithm that either:
+
+-   gives a finite set of regular expressions (or finite automata) that
+    describes their equivalence classes, or
+-   rejects the rewrites.
+
+We would be willing to reject cases where there are more equivalence classes
+than rewrite rules. An example of this would be the rewrite rules `A == XY` and
+`B == YZ` have equivalence classes `A|XY`, `B|YZ`, `XYZ|AZ|XB`.
+
+## Problem
+
+In particular, we've identified two operations for combining two finite automata
+depending on how they overlap:
+
+-   **Containment operation:** If we have automatas `X` and `Y`, where `Y`
+    completely matches a subsequence matched by `X`, then we need to extend `X`
+    to include all the rewrites offered by `Y`. For example, if `X` is `AB*` and
+    `Y` is `(B|C)`, then we can extend `X` to `A(B|C)*`.
+-   **Union operation:** If we have automatas `X` and `Y`, and there is a string
+    `ABC` such that `X` matches the prefix `AB` and `Y` matches the suffix `BC`,
+    then we need to make sure there is a rule for matching `ABC`.
+
+Even though it is straightforward to translate a single rewrite rule into a
+regular expression that matches at least some portion of the equivalence class,
+to match the entire equivalence classes we need to apply these two operations in
+ways that can run into difficulties.
+
+One concern is that an individual regular expression might not cover the entire
+equivalence for a rewrite rule. We may need to apply the union and containment
+operations to combine the automata _with itself_. For example, we may convert
+the rewrite rule `AB == BA` into the regular expression `AB|BA`, but this
+doesn't completely describe the set of equivalence classes that this rule
+creates, since it triggers the union operation twice to add `AAB|ABA|BAA` and
+`ABB|BAB|BBA`. These would also trigger the union operation, and so on
+indefinitely, so the rewrite rule `AB == BA` does not have a finite set of
+regular equivalence classes. Similarly the rule `A == BCB` has an infinite
+family of equivalence classes: `BCB|A`, `BCBCB|ACB|BCA`,
+`BCBCBCB|ACBCB|ACA|BCACB|BCBCA`, ...
+
+The other concern is that even in the case that each rule by itself can be
+faithfully translated into a regular expression that captures the equivalence
+classes of the rewrite, attempts to combine multiple rules using the two
+operations might never reach a fixed point.
+
+In both cases, we are looking for criteria that we can use to decide if the
+application of the operations will terminate. If it would not terminate, then we
+need to report an error to the user instead of applying those two operations
+endlessly.
+
+## Examples
+
+### Regular single rules
+
+-   Anything where there are no shared letters between the two sides, and no
+    side has a prefix matching a suffix
+    -   `A == B` => `A|B`
+    -   `A == BC` => `A|BC`
+    -   `A == BBC` => `A|BBC`
+    -   `A == BCC` => `A|BCC`
+-   `A == AB` => `AB*`
+-   `A == BA` => `B*A`
+-   `A == AA` => `AA*` or `A*A` or `A+`
+-   `A == ABC` => `A(BC)*`
+-   `A == ABB` => `A(BB)*`
+-   `A == AAA` => `A(AA)*`
+-   `A == BCA` => `(BC)*A`
+-   `A == BBA` => `(BB)*A`
+-   `A == ACA` => `A(CA)*` or `(AC)*A`
+
+### Regular combinations
+
+-   `A==AB` + `A==AC` => `AB*` + `AC*` => `A(B|C)*`
+-   `A==AB` + `B==BC` => `AB*` + `BC*` => `A(B|C)*`, `BC*` with relation
+    `A(B|C)* BC* == A(B|C)*`; Note: `A == AB == ABC == AC`
+-   `A==AB` + `C==CB` => `AB*`, `CB*`, no relations
+-   `A==AB` + `A==CA` => `AB*` + `C*A` => `C*AB*`
+-   `A==AB` + `C==BC` => `AB*` + `B*C` => `AB*`, `B*C`, where if you have `AB*C`
+    it doesn't matter how you split the `B`s between `AB*` and `B*C`.
+-   `A==AB` + `B==AB` => `AB*` + `A*B` => `(A|B)+`
+-   `A==AB` + `B==CB` => `AB*` + `C*B` => `A(C*B)*`, `C*B` with `A(C*B)* C*B` ->
+    `A(C*B)*`
+-   `A==AB` + `B==CB` + `C==CD` => `AB*` + `C*B` + `CD*` => `A((CD*)*B)*`,
+    `(CD*)*B`, `CD*`
+-   `A==ABC` + `D==CB` => `A(BC)*` + `(CB|D)` => `A(BD*C)*`, `A(BD*C)*BD*`,
+    `(CB|D)`
+-   `A==ABBBBB` + `A==ABBBBBBBB` (or `A==AB^5` + `A==AB^8`)=> `A(BBBBB)*` +
+    `A(BBBBBBBB)*` => `AB*`, since `A=AB^8=AB^16=AB^11=AB^6=AB`
+
+### Not regular
+
+-   `AB == BA`, has equivalence classes: `AB|BA`, `AAB|ABA|BAA`, `ABB|BAB|BBA`,
+    ...
+-   Similarly: `AB==C` + `C==BA`
+-   `ABC == CBA`, has equivalence classes: `ABC|CBA`, `ABABC|ABCBA|CBABA`,
+    `ABCBC|CBABC|CBCBA`, ...
+-   `A == BAB`, involves back references or counting the number of `B`s:
+    `A|BAB|BBABB|BBBABBB|`...
+-   `A == BAC`, involves matching the number of `B`s to `C`s:
+    `A|BAC|BBACC|BBBACCC|`...
+-   `A == BCB`, has equivalence classes: `BCB|A`, `BCBCB|ACB|BCA`,
+    `BCBCBCB|ACBCB|ACA|BCACB|BCBCA`, ...
+-   `A == BB`, has equivalence classes: `A|BB`, `BBB|AB|BA`, `(A|BB)(A|BB)|BAB`,
+    ...
+-   `A == BBB`, has equivalence classes: `A|BBB`, `BBBB|AB|BA`,
+    `BBBBB|ABB|BAB|BBA`, ...
+-   `AA == BB`, has equivalence classes: `AA|BB`, `AAA|ABB|BBA`, `BAA|BBB|AAB`,
+    ...
+-   `AB == AA`, has equivalence classes: `A(A|B)`, `A(A|B)(A|B)`,
+    `A(A|B)(A|B)(A|B)`, ...
+-   `AB == BB`, similarly
+-   `AB == BC`, has equivalence classes: `(AB|BC)`, `(AAB|ABC|BCC)`, ...,
+    `A`^i`BC`^(N-i)
+-   `A == AAB` and `A == BAA`
+
+## References
+
+See these notes from discussions:
+
+-   [Regular equivalence classes 2021-09-23](https://docs.google.com/document/d/1iyL7ZDfWT6ZmDSz6Fp8MrLcl5nOQc4ItQbeL3QlVXYU/edit#)
+-   [Carbon minutes (rolling): Open discussions: 2021-09-27](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.qh4b1gy7o5el)
+-   [Carbon minutes (rolling): Open discussions: 2021-09-28](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.9a1r39mla9ho)
+-   [Regular equivalence classes 2 2021-10-01](https://docs.google.com/document/d/1xGDRSV6q534-CoDZnzuZJmzhty2yPki2ZfDDPLSHYek/edit#)

Неке датотеке нису приказане због велике количине промена