Bläddra i källkod

Generic details 11: operator overloading (#1144)

Operators rewrite to calls of specific operator interface functions, so you overload an operator for a type by implementing an interface for it. There is a `like` operator for defining a set if implementations for supporting implicit conversions more conveniently.

Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b 4 år sedan
förälder
incheckning
db66a4350e

+ 12 - 8
docs/design/classes.md

@@ -56,13 +56,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
         -   [Friends](#friends)
         -   [Test friendship](#test-friendship)
         -   [Access control for construction](#access-control-for-construction)
+    -   [Operator overloading](#operator-overloading)
 -   [Future work](#future-work)
     -   [Struct literal shortcut](#struct-literal-shortcut)
     -   [Optional named parameters](#optional-named-parameters)
         -   [Field defaults for struct types](#field-defaults-for-struct-types)
         -   [Destructuring in pattern matching](#destructuring-in-pattern-matching)
         -   [Discussion](#discussion)
-    -   [Operator overloading](#operator-overloading)
     -   [Inheritance](#inheritance-1)
         -   [Destructors](#destructors)
         -   [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces)
@@ -1548,6 +1548,17 @@ if it has access to (write) all of its fields.
 even when it only has public fields. This will be resolved in question-for-leads
 issue [#803](https://github.com/carbon-language/carbon-lang/issues/803).
 
+### Operator overloading
+
+Developers may define how standard Carbon operators, such as `+` and `/`, apply
+to custom types by implementing the
+[interface](generics/terminology.md#interface) that corresponds to that operator
+for the types of the operands. See the
+["operator overloading" section](generics/details.md#operator-overloading) of
+the [generics design](generics/overview.md). The specific interface used for a
+given operator may be found in the
+[expressions design](/docs/design/expressions/README.md).
+
 ## Future work
 
 This includes features that need to be designed, questions to answer, and a
@@ -1636,13 +1647,6 @@ Some discussion on this topic has occurred in:
     [2](https://docs.google.com/document/d/1u6GORSkcgThMAiYKOqsgALcEviEtcghGb5TTVT-U-N0/edit)
 -   ["match" in syntax choices doc](https://docs.google.com/document/d/1iuytei37LPg_tEd6xe-O6P_bpN7TIbEjNtFMLYW2Nno/edit#heading=h.y566d16ivoy2)
 
-### Operator overloading
-
-This includes destructors, copy and move operations, as well as other Carbon
-operators such as `+` and `/`. We expect types to implement these operations by
-implementing corresponding interfaces, see
-[the generics overview](generics/overview.md).
-
 ### Inheritance
 
 #### Destructors

+ 347 - 6
docs/design/generics/details.md

@@ -95,6 +95,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Interface members with definitions](#interface-members-with-definitions)
     -   [Interface defaults](#interface-defaults)
     -   [`final` members](#final-members)
+-   [Operator overloading](#operator-overloading)
+    -   [Binary operators](#binary-operators)
+    -   [`like` operator for implicit conversions](#like-operator-for-implicit-conversions)
 -   [Future work](#future-work)
     -   [Dynamic types](#dynamic-types)
         -   [Runtime type parameters](#runtime-type-parameters)
@@ -102,7 +105,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Abstract return types](#abstract-return-types)
     -   [Evolution](#evolution)
     -   [Testing](#testing)
-    -   [Operator overloading](#operator-overloading)
     -   [Impls with state](#impls-with-state)
     -   [Generic associated types and higher-ranked types](#generic-associated-types-and-higher-ranked-types)
         -   [Generic associated types](#generic-associated-types)
@@ -4440,6 +4442,349 @@ There are a few reasons for this feature:
 
 Note that this applies to associated entities, not interface parameters.
 
+## Operator overloading
+
+Operations are overloaded for a type by implementing an interface specific to
+that interface for that type. For example, types implement the `Negatable`
+interface to overload the unary `-` operator:
+
+```
+// Unary `-`.
+interface Negatable {
+  let Result:! Type = Self;
+  fn Negate[me: Self]() -> Result;
+}
+```
+
+Expressions using operators are rewritten into calls to these interface methods.
+For example, `-x` would be rewritten to `x.(Negatable.Negate)()`.
+
+The interfaces and rewrites used for a given operator may be found in the
+[expressions design](/docs/design/expressions/README.md).
+[Question-for-leads issue #1058](https://github.com/carbon-language/carbon-lang/issues/1058)
+defines the naming scheme for these interfaces.
+
+### Binary operators
+
+Binary operators will have an interface that is
+[parameterized](#parameterized-interfaces) based on the second operand. For
+example, to say a type may be converted to another type using an `as`
+expression, implement the
+[`As` interface](/docs/design/expressions/as_expressions.md#extensibility):
+
+```
+interface As(Dest:! Type) {
+  fn Convert[me: Self]() -> Dest;
+}
+```
+
+The expression `x as U` is rewritten to `x.(As(U).Convert)()`. Note that the
+parameterization of the interface means it can be implemented multiple times to
+support multiple operand types.
+
+Unlike `as`, for most binary operators the interface's argument will be the
+_type_ of the right-hand operand instead of its _value_. Consider an interface
+for a binary operator like `*`:
+
+```
+// Binary `*`.
+interface MultipliableWith(U:! Type) {
+  let Result:! Type = Self;
+  fn Multiply[me: Self](other: U) -> Result;
+}
+```
+
+A use of binary `*` in source code will be rewritten to use this interface:
+
+```
+var left: Meters = ...;
+var right: f64 = ...;
+var result: auto = left * right;
+// Equivalent to:
+var equivalent: left.(MultipliableWith(f64).Result)
+    = left.(MultipliableWith(f64).Multiply)(right);
+```
+
+Note that if the types of the two operands are different, then swapping the
+order of the operands will result in a different implementation being selected.
+It is up to the developer to make those consistent when that is appropriate. The
+standard library will provide [adapters](#adapting-types) for defining the
+second implementation from the first, as in:
+
+```
+interface ComparableWith(RHS:! Type) {
+  fn Compare[me: Self](right: RHS) -> CompareResult;
+}
+
+adapter ReverseComparison
+    (T:! Type, U:! ComparableWith(RHS)) for T {
+  impl as ComparableWith(U) {
+    fn Compare[me: Self](right: RHS) -> CompareResult {
+      return ReverseCompareResult(right.Compare(me));
+    }
+  }
+}
+
+external impl SongByTitle as ComparableWith(SongTitle);
+external impl SongTitle as ComparableWith(SongByTitle)
+    = ReverseComparison(SongTitle, SongByTitle);
+```
+
+In some cases the reverse operation may not be defined. For example, a library
+might support subtracting a vector from a point, but not the other way around.
+
+Further note that even if the reverse implementation exists,
+[the impl prioritization rule](#prioritization-rule) might not pick it. For
+example, if we have two types that support comparison with anything implementing
+an interface that the other implements:
+
+```
+interface IntLike {
+  fn AsInt[me: Self]() -> i64;
+}
+
+class EvenInt { ... }
+external impl EvenInt as IntLike;
+external impl EvenInt as ComparableWith(EvenInt);
+// Allow `EvenInt` to be compared with anything that
+// implements `IntLike`, in either order.
+external impl [T:! IntLike] EvenInt as ComparableWith(T);
+external impl [T:! IntLike] T as ComparableWith(EvenInt);
+
+class PositiveInt { ... }
+external impl PositiveInt as IntLike;
+external impl PositiveInt as ComparableWith(PositiveInt);
+// Allow `PositiveInt` to be compared with anything that
+// implements `IntLike`, in either order.
+external impl [T:! IntLike] PositiveInt as ComparableWith(T);
+external impl [T:! IntLike] T as ComparableWith(PositiveInt);
+```
+
+Then it will favor selecting the implementation based on the type of the
+left-hand operand:
+
+```
+var even: EvenInt = ...;
+var positive: PositiveInt = ...;
+// Uses `EvenInt as ComparableWith(T)` impl
+if (even < positive) { ... }
+// Uses `PositiveInt as ComparableWith(T)` impl
+if (positive > even) { ... }
+```
+
+### `like` operator for implicit conversions
+
+Because the type of the operands is directly used to select the implementation
+to use, there are no automatic implicit conversions, unlike with function or
+method calls. Given both a method and an interface implementation for
+multiplying by a value of type `f64`:
+
+```
+class Meters {
+  fn Scale[me: Self](s: f64) -> Self;
+}
+// "Implementation One"
+external impl Meters as MultipliableWith(f64)
+    where .Result = Meters {
+  fn Multiply[me: Self](other: f64) -> Result {
+    return me.Scale(other);
+  }
+}
+```
+
+the method will work with any argument that can be implicitly converted to `f64`
+but the operator overload will only work with values that have the specific type
+of `f64`:
+
+```
+var height: Meters = ...;
+var scale: f32 = 1.25;
+// ✅ Allowed: `scale` implicitly converted
+//             from `f32` to `f64`.
+var allowed: Meters = height.Scale(scale);
+// ❌ Illegal: `Meters` doesn't implement
+//             `MultipliableWith(f32)`.
+var illegal: Meters = height * scale;
+```
+
+The workaround is to define a parameterized implementation that performs the
+conversion. The implementation is for types that implement the
+[`ImplicitAs` interface](/docs/design/expressions/implicit_conversions.md#extensibility).
+
+```
+// "Implementation Two"
+external impl [T:! ImplicitAs(f64)]
+    Meters as MultipliableWith(T) where .Result = Meters {
+  fn Multiply[me: Self](other: T) -> Result {
+    // Carbon will implicitly convert `other` from type
+    // `T` to `f64` to perform this call.
+    return me.(Meters.(MultipliableWith(f64).Multiply))(other);
+  }
+}
+// ✅ Allowed: uses `Meters as MultipliableWith(T)` impl
+//             with `T == f32` since `f32 is ImplicitAs(f64)`.
+var now_allowed: Meters = height * scale;
+```
+
+Observe that the [prioritization rule](#prioritization-rule) will still prefer
+the unparameterized impl when there is an exact match.
+
+To reduce the boilerplate needed to support these implicit conversions when
+defining operator overloads, Carbon has the `like` operator. This operator can
+only be used in the type or type-of-type part of an `impl` declaration, as part
+of a forward declaration or definition, in a place of a type.
+
+```
+// Notice `f64` has been replaced by `like f64`
+// compared to "implementation one" above.
+external impl Meters as MultipliableWith(like f64)
+    where .Result = Meters {
+  fn Multiply[me: Self](other: f64) -> Result {
+    return me.Scale(other);
+  }
+}
+```
+
+This `impl` definition actually defines two implementations. The first is the
+same as this definition with `like f64` replaced by `f64`, giving something
+equivalent to "implementation one". The second implementation replaces the
+`like f64` with a parameter that ranges over types that can be implicitly
+converted to `f64`, equivalent to "implementation two".
+
+In general, each `like` adds one additional impl. There is always the impl with
+all of the `like` expressions replaced by their arguments with the definition
+supplied in the source code. In addition, for each `like` expression, there is
+an impl with it replaced by a new parameter. These additional impls will
+delegate to the main impl, which will trigger implicit conversions according to
+[Carbon's ordinary implicit conversion rules](/docs/design/expressions/implicit_conversions.md).
+In this example, there are two uses of `like`, producing three implementations
+
+```
+external impl like Meters as MultipliableWith(like f64)
+    where .Result = Meters {
+  fn Multiply[me: Self](other: f64) -> Result {
+    return me.Scale(other);
+  }
+}
+```
+
+is equivalent to "implementation one", "implementation two", and:
+
+```
+external impl [T:! ImplicitAs(Meters)]
+    T as MultipliableWith(f64) where .Result = Meters {
+  fn Multiply[me: Self](other: f64) -> Result {
+    // Will implicitly convert `me` to `Meters` in order to
+    // match the signature of this `Multiply` method.
+    return me.(Meters.(MultipliableWith(f64).Multiply))(other);
+  }
+}
+```
+
+`like` may be used in forward declarations in a way analogous to impl
+definitions.
+
+```
+external impl like Meters as MultipliableWith(like f64)
+    where .Result = Meters;
+}
+```
+
+is equivalent to:
+
+```
+// All `like`s removed. Same as the declaration part of
+// "implementation one", without the body of the definition.
+external impl Meters as MultipliableWith(f64)
+    where .Result = Meters;
+
+// First `like` replaced with a wildcard.
+external impl [T:! ImplicitAs(Meters)]
+    T as MultipliableWith(f64) where .Result = Meters;
+
+// Second `like` replaced with a wildcard. Same as the
+// declaration part of "implementation two", without the
+// body of the definition.
+external impl [T:! ImplicitAs(f64)]
+    Meters as MultipliableWith(T) where .Result = Meters;
+```
+
+In addition, the generated impl definition for a `like` is implicitly injected
+at the end of the (unique) source file in which the impl is first declared. That
+is, it is injected in the API file if the impl is declared in an API file, and
+in the sole impl file declaring the impl otherwise. This means an `impl`
+declaration using `like` in an API file also makes the parameterized definition
+
+If one `impl` declaration uses `like`, other declarations must use `like` in the
+same way to match.
+
+The `like` operator may be nested, as in:
+
+```
+external impl like Vector(like String) as Printable;
+```
+
+Which will generate implementations with declarations:
+
+```
+external impl Vector(String) as Printable;
+external impl [T:! ImplicitAs(Vector(String))] T as Printable;
+external impl [T:! ImplicitAs(String)] Vector(T) as Printable;
+```
+
+The generated implementations must be legal or the `like` is illegal. For
+example, it must be legal to define those impls in this library by the
+[orphan rule](#orphan-rule). In addition, the generated `impl` definitions must
+only require implicit conversions that are guaranteed to exist. For example,
+there existing an implicit conversion from `T` to `String` does not imply that
+there is one from `Vector(T)` to `Vector(String)`, so the following use of
+`like` is illegal:
+
+```
+// ❌ Illegal: Can't convert a value with type
+//             `Vector(T:! ImplicitAs(String))`
+//             to `Vector(String)` for `me`
+//             parameter of `Printable.Print`.
+external impl Vector(like String) as Printable;
+```
+
+Since the additional implementation definitions are generated eagerly, these
+errors will be reported in the file with the first declaration.
+
+The argument to `like` must either not mention any type parameters, or those
+parameters must be able to be determined due to being repeated outside of the
+`like` expression.
+
+```
+// ✅ Allowed: no parameters
+external impl like Meters as Printable;
+
+// ❌ Illegal: No other way to determine `T`
+external impl [T:! IntLike] like T as Printable;
+
+// ❌ Illegal: `T` being used in a `where` clause
+//             is insufficient.
+external impl [T:! IntLike] like T
+    as MultipliableWith(i64) where .Result = T;
+
+// ❌ Illegal: `like` can't be used in a `where`
+//             clause.
+external impl Meters as MultipliableWith(f64)
+    where .Result = like Meters;
+
+// ✅ Allowed: `T` can be determined by another
+//             part of the query.
+external impl [T:! IntLike] like T
+    as MultipliableWith(T) where .Result = T;
+external impl [T:! IntLike] T
+    as MultipliableWith(like T) where .Result = T;
+
+// ✅ Allowed: Only one `like` used at a time, so this
+//             is equivalent to the above two examples.
+external impl [T:! IntLike] like T
+    as MultipliableWith(like T) where .Result = T;
+```
+
 ## Future work
 
 ### Dynamic types
@@ -4489,11 +4834,6 @@ supported and made safe.
 The idea is that you would write tests alongside an interface that validate the
 expected behavior of any type implementing that interface.
 
-### Operator overloading
-
-We will need a story for defining how an operation is overloaded for a type by
-implementing an interface for that type.
-
 ### Impls with state
 
 A feature we might consider where an `impl` itself can have state.
@@ -4570,3 +4910,4 @@ be included in the declaration as well.
 -   [#983: Generic details 7: final impls](https://github.com/carbon-language/carbon-lang/pull/983)
 -   [#990: Generics details 8: interface default and final members](https://github.com/carbon-language/carbon-lang/pull/990)
 -   [#1013: Generics: Set associated constants using where constraints](https://github.com/carbon-language/carbon-lang/pull/1013)
+-   [#1144: Generic details 11: operator overloading](https://github.com/carbon-language/carbon-lang/pull/1144)

+ 19 - 2
docs/design/generics/overview.md

@@ -35,6 +35,7 @@ pointers to other design documents that dive deeper into individual topics.
         -   [Parameterized interfaces](#parameterized-interfaces)
     -   [Constraints](#constraints)
     -   [Parameterized impls](#parameterized-impls)
+    -   [Operator overloading](#operator-overloading)
 -   [Future work](#future-work)
 -   [References](#references)
 
@@ -627,14 +628,30 @@ library defining some name from its type structure. If a library defines
 multiple implementations with the same type structure, they must be listed in
 priority order in a prioritization block.
 
+### Operator overloading
+
+To overload an operator, implement the corresponding interface from the standard
+library. For example, to define how the unary `-` operator behaves for a type,
+implement the `Negatable` interface for that type. The interfaces and rewrites
+used for a given operator may be found in the
+[expressions design](/docs/design/expressions/README.md).
+
+As a convenience, there is a shorcut for defining an implementation that
+supports any type implicitly convertible to a specified type, using `like`:
+
+```
+// Support multiplying values of type `Distance` with
+// values of type `f64` or any type implicitly
+// convertible to `f64`.
+external impl Distance as MultipliableWith(like f64) ...
+```
+
 ## Future work
 
 -   Support functions should have a way to accept types that types that vary at
     runtime.
 -   You should have the ability to mark entities 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

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

@@ -61,6 +61,7 @@ The following words are interpreted as keywords:
 -   `is`
 -   `let`
 -   `library`
+-   `like`
 -   `match`
 -   `namespace`
 -   `not`

+ 157 - 0
proposals/p1144.md

@@ -0,0 +1,157 @@
+# Generic details 11: operator overloading
+
+<!--
+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/1144)
+
+<!-- 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)
+    -   [Weak impls instead of adapters for reverse implementations](#weak-impls-instead-of-adapters-for-reverse-implementations)
+    -   [Default impls instead of adapters for reverse implementations](#default-impls-instead-of-adapters-for-reverse-implementations)
+    -   [Allow an impl declaration with `like` to match one without](#allow-an-impl-declaration-with-like-to-match-one-without)
+    -   [Where are the impl definitions from `like` generated?](#where-are-the-impl-definitions-from-like-generated)
+    -   [Support marking interfaces or their members as `external`](#support-marking-interfaces-or-their-members-as-external)
+
+<!-- tocstop -->
+
+## Problem
+
+C++ supports
+[operator overloading](https://en.wikipedia.org/wiki/Operator_overloading), and
+we would like Carbon to as well. This proposal is about the general problem, not
+the specifics application to any particular operator.
+
+This proposal does not attempt to define a mechanism by which we can ensure that
+`a < b` has the same value as `b > a`.
+
+## Background
+
+The generics feature is the
+[single static open extension mechanism](/docs/project/principles/static_open_extension.md)
+in Carbon, and so will be what we use operator overloading. We have already
+started specifying the ability to extend or customize the behavior of operators
+by implementing interfaces, as in these proposals:
+
+-   [#820: Implicit conversions](https://github.com/carbon-language/carbon-lang/pull/820)
+-   [#845: as expressions](https://github.com/carbon-language/carbon-lang/pull/845)
+-   [#911: Conditional expressions](https://github.com/carbon-language/carbon-lang/pull/911)
+-   [#1083: Arithmetic expressions](https://github.com/carbon-language/carbon-lang/pull/1083)
+
+Proposal
+[#702: Comparison operators](https://github.com/carbon-language/carbon-lang/pull/702)
+specified
+[using interfaces for overloading the comparison operators](p0702.md#overloading),
+but did not pin down specifically what those interfaces are.
+
+## Proposal
+
+This proposal adds an
+["Operator overloading" section](/docs/design/generics/details.md#operator-overloading)
+to the [detailed design of generics](/docs/design/generics/details.md).
+
+## Rationale based on Carbon's goals
+
+This proposal advances Carbon's goals:
+
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write),
+    by making common constructs more concise, and allowing the syntax to more
+    closely mirror notation used math or the application domain.
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution),
+    since this allows standard types to implement operators using the same
+    mechanisms that user types do, this allows changes between what is built-in
+    versus provided in a library without user-visible impact.
+
+## Alternatives considered
+
+The current proposal requires the user to define a reverse implementation, and
+recommends using an adapter to do that more conveniently. We also considered
+approaches that would provide the reverse implementation more automatically.
+
+### Weak impls instead of adapters for reverse implementations
+
+We proposed
+[weak impls](https://github.com/carbon-language/carbon-lang/pull/1027) as a way
+of defining blanket impls for the reverse impl that did not introduce
+[cycles](/docs/design/generics/details.md#acyclic-rule). We rejected that
+approach due to giving the reverse implementation the wrong priority. This meant
+that there were many situations where `a < b` and `b > a` would give different
+answers.
+
+### Default impls instead of adapters for reverse implementations
+
+We then proposed
+[default impls](https://github.com/carbon-language/carbon-lang/pull/1034) as a
+way to define reverse implementations. These were rejected because they had a
+lot of overlap with blanket impls, making it difficult to describe when to use
+one over the other, and because they introduced a lot of complexity without
+fully solving the priority problem. Most of the complexity was from the criteria
+for determining whether the default implementation would be used. As
+[noted](/docs/design/generics/details.md#binary-operators), the current proposal
+still has some priority issues, but this way the relevant impls are visible in
+the source which will hopefully make it clearer why it happens.
+
+The capability provided by default impls -- the ability to conveniently give
+implementations of other interfaces -- may prove useful enough that we would
+reconsider this decision in the future.
+
+### Allow an impl declaration with `like` to match one without
+
+We considered allowing an impl declared with `like` to match the equivalent
+impls without `like`. The main concern was there would not be a canonical form
+without `like`, particularly of how the newly introduced parameter would be
+written. We thought we might say that the `like` declaration, since it omits a
+spelling of the parameter, is allowed to match any spelling of the parameter.
+However, there would still be a question of whether to use a deduced parameter,
+as in `[T:! ImplicitAs(i64)] Vector(T)` or not as in
+`Vector(T:! ImplicitAs(i64))`. We also considered the canonical form of
+`Vector(_:! ImplicitAs(i64))` without naming the parameter. In the end, we
+decided to start with a restrictive approach with the knowledge that we could
+change once we gained experience.
+
+The main use case for allowing declarations in a different form, which may
+motivate changes in the future, is to prioritize the different implementations
+generated by the `like` shortcut separately in `match_first` blocks.
+
+This was discussed in
+[open discussion on 2022-03-24](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#).
+
+### Where are the impl definitions from `like` generated?
+
+We considered whether the additional impl definitions would be generated with
+the first declaration of an impl using `like` or with its definition. We
+ultimately decided on the former approach for two reasons:
+
+-   The generated impl definitions are parameterized even if the explicit
+    definition is not, and parameterized impl definitions may need to be in the
+    API file to allow separate compilation.
+-   This will make the code doing implicit conversions visible to callers,
+    allowing it to be inlined, matching how the caller does implicit conversions
+    for method calls.
+
+This was discussed in
+[the #generics channel on Discord](https://discord.com/channels/655572317891461132/941071822756143115/962059164014739526).
+
+### Support marking interfaces or their members as `external`
+
+We
+[discussed on 2022-03-28](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.sk06n1ggoa3o)
+the idea that operator interfaces might be marked `external`. This would either
+mean that types would only be able to implement them using `external impl` or
+that even if they were implemented internally, they would not add the names of
+the interface members to the type. Alternatively, individual members might be
+marked `external` to indicate that their names are not added to implementing
+types, which might also be useful for making changes to an interface in a
+compatible way.
+
+We were not sure if this feature was needed, so we left this as future work.