diff --git a/docs/design/classes.md b/docs/design/classes.md index 21bd472ea599..0697778d2951 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -57,6 +57,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Partial class type](#partial-class-type) - [Usage](#usage) - [Assignment with inheritance](#assignment-with-inheritance) + - [Compatible types](#compatible-types) + - [Adapters](#adapters) - [Destructors](#destructors) - [Access control](#access-control) - [Private access](#private-access) @@ -1648,6 +1650,102 @@ implement it for final types. However, following the we allow users to also implement assignment on extensible classes, even though it can lead to [slicing](https://en.wikipedia.org/wiki/Object_slicing). +### Compatible types + +Two types are compatible if they have the same notional set of values and +represent those values in the same way, even if they expose different APIs. The +representation of a type describes how the values of that type are represented +as a sequence of bits in memory. The set of values of a type includes properties +that the compiler can't directly see, such as invariants that the type +maintains. + +We can't just say two types are compatible based on structural reasons. Instead, +we have specific constructs that create compatible types from existing types in +ways that encourage preserving the programmer's intended semantics and +invariants, such as implementing the API of the new type by calling (public) +methods of the original API, instead of accessing any private implementation +details. + +Casting a value between compatible types is safe without any dynamic checks or +danger of [object slicing](https://en.wikipedia.org/wiki/Object_slicing). + +#### Adapters + +An adapter creates a new type compatible with an existing type, but with a +different API. Adapters are defined by using the `adapt` keyword inside a +`class` definition: + +```carbon +class Song { + fn Title(self) -> String; +} +class SongByTitle { + adapt Song; +} +``` + +The rules for adapters are: + +- You can add any declaration that you could add to a class except for + declarations that would change the representation of the type. This means + you can add methods, functions, interface implementations, and aliases, but + not fields, base classes, or virtual functions. The specific implementations + of virtual functions are part of the type representation, and so no virtual + functions may be overridden in an adapter either. +- The adapted type is compatible with the original type, and that relationship + is an equivalence class, so `Song`, `SongByTitle`, and any other adapters of + `Song` end up compatible with each other. +- Since adapted types are compatible with the original type, you may + explicitly cast between them, but there is no implicit conversion between + these types. + +Inside an adapter, the `Self` type matches the adapter. Members of the original +type may be accessed by a cast: + +```carbon +class SongByTitle { + adapt Song; + fn Less(self, rhs: Self) -> bool { + return (self as Song).Title() < (rhs as Song).Title(); + } +} +``` + +An adapter can also preserve the API and interface implementations of the original +type using `extend adapt`. For details on how an extending adapter implements +interfaces that are implemented for the adapted type, as well as applications of adapters to generics, see +[Adapting types](/docs/design/generics/details.md#adapting-types) in the generics +design. + +**Comparison with other languages:** This is similar to the Rust idiom called +"newtype", which is used to implement traits on types while avoiding +[coherence](/docs/design/generics/terminology.md#coherence) problems, see +[here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) +and +[here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial). +Rust's mechanism doesn't directly support reusing implementations, though some +of that is provided by macros defined in libraries. + +Rust also uses the newtype idiom to create types with additional invariants or +other information encoded in the type +([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html), +[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction), +[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)). +This is used to record in the type system that some data has passed validation +checks, like `ValidDate` with the same data layout as `Date`. Or to record the +units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet` +versus `Meters`. + +> **Future work:** We should have some way of restricting the casts between a type +> and an adapter to address this use case. One possibility would be to add the +> keyword `private` before `adapt`, so you might write +> `extend private adapt Date;`. + +Haskell has a [`newtype` feature](https://wiki.haskell.org/Newtype) as well. +Haskell's feature doesn't directly support reusing implementations either, but +the most popular compiler provides it as +[an extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html). + ### Destructors Every non-abstract type is _destructible_, meaning has a defined destructor @@ -1765,8 +1863,7 @@ interface Allocator { To pass a pointer to a base class without a virtual destructor to a checked-generic function expecting a `Deletable` type, use the -`UnsafeAllowDelete` -[type adapter](/docs/design/generics/details.md#adapting-types). +`UnsafeAllowDelete` [type adapter](#adapters). ``` class UnsafeAllowDelete(T: Concrete) { @@ -2323,6 +2420,10 @@ the type of `U.x`." - [Nominal data class](/proposals/p000722-nominal-classes-and-methods.md#nominal-data-class) - [Let constants](/proposals/p000722-nominal-classes-and-methods.md#let-constants) +- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731) + + - [`adaptor` instead of `adapter`](/proposals/p000731-generics-details-2-adapters-associated-types-parameterized-interfaces.md#adaptor-instead-of-adapter) + - [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777) - [Classes are final by default](/proposals/p000777-inheritance.md#classes-are-final-by-default) @@ -2376,6 +2477,8 @@ the type of `U.x`." - [Use `extends` instead of `extend`](/proposals/p002760-consistent-class-and-interface-syntax.md#use-extends-instead-of-extend) - [List base class in class declaration](/proposals/p002760-consistent-class-and-interface-syntax.md#list-base-class-in-class-declaration) + - [Continue to use `adapter` or `adaptor` instead of `adapt`](/proposals/p002760-consistent-class-and-interface-syntax.md#continue-to-use-adapter-or-adaptor-instead-of-adapt) + - [Use some other syntax for extending adapters](/proposals/p002760-consistent-class-and-interface-syntax.md#use-some-other-syntax-for-extending-adapters) - [#5017: Destructor syntax](https://github.com/carbon-language/carbon-lang/pull/5017) @@ -2401,6 +2504,7 @@ the type of `U.x`." - [#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257) - [#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561) - [#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722) +- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731) - [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777) - [#875: Principle: Information accumulation](https://github.com/carbon-language/carbon-lang/pull/875) - [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index bdc3b5110e96..584b1a05efe1 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -153,7 +153,7 @@ Lossy conversions between `iN` or `uN` and `iM` or `uM` are not supported with The following conversion is supported by `as`: - `T` -> `U` if `T` is - [compatible](../generics/terminology.md#compatible-types) with `U`. + [compatible](/docs/design/classes.md#compatible-types) with `U`. **Future work:** We may need a mechanism to restrict which conversions between adapters are permitted and which code can perform them. Some of the conversions @@ -162,7 +162,7 @@ permitted by this rule may only be allowed in certain contexts. ## Extensibility Explicit casts can be defined for user-defined types such as -[classes](../classes.md) by implementing the `As` interface: +[classes](/docs/design/classes.md) by implementing the `As` interface: ``` interface As(Dest: type) { diff --git a/docs/design/generics/appendix-coherence.md b/docs/design/generics/appendix-coherence.md index ac521b0686fb..de5eeb921d9a 100644 --- a/docs/design/generics/appendix-coherence.md +++ b/docs/design/generics/appendix-coherence.md @@ -46,7 +46,7 @@ These last two cases are highlighted as concerns in Rust in Since Carbon is bundling interface implementations into types, for the convenience and expressiveness that provides, we satisfy those use cases by giving the user control over the type of a value. This means having facilities -for defining new [compatible types](terminology.md#compatible-types) with +for defining new [compatible types](/docs/design/classes.md#compatible-types) with different interface implementations, and casting between those types as needed. ## The "Hashtable Problem" diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 867f256086f7..443ede744ecd 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -45,7 +45,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types) - [Use case: Private impl](#use-case-private-impl) - [Use case: Accessing interface names](#use-case-accessing-interface-names) - - [Future work: Adapter with stricter invariants](#future-work-adapter-with-stricter-invariants) - [Associated constants](#associated-constants) - [Associated functions](#associated-functions) - [Associated facets](#associated-facets) @@ -317,7 +316,7 @@ Assert(p1.Add(p1) == p2); ``` For more on how `extend` affects member access, see -[member access](../expressions/member_access.md#extend). +[member access](/docs/design/expressions/member_access.md#extend). Without `extend`, those methods may only be accessed with [qualified member names and compound member access](#qualified-member-names-and-compound-member-access): @@ -1433,7 +1432,7 @@ fn DoHashAndEquals[T: Hashable](x: T) { > interface doesn't impl the interfaces it extends, as adopted in > [#5168: Forward `impl` declaration of an incomplete interface](/proposals/p005168-forward-impl-declaration-of-an-incomplete-interface.md). > Should link to -> [`extend` in member access](../expressions/member_access.md#extend). +> [`extend` in member access](/docs/design/expressions/member_access.md#extend). When implementing an interface, we allow implementing the aliased names as well. In the case of `Hashable` above, this includes all the members of `Equatable`, @@ -1800,10 +1799,9 @@ be detected in function overloading. Since interfaces may only be implemented for a type once, and we limit where implementations may be added to a type, there is a need to allow the user to switch the type of a value to access different interface implementations. Carbon -therefore provides a way to create new types -[compatible with](terminology.md#compatible-types) existing types with different -APIs, in particular with different interface implementations, by -[adapting](terminology.md#adapting-a-type) them: +therefore provides [adapters](/docs/design/classes.md#adapters) as a way to create new types +[compatible with](/docs/design/classes.md#compatible-types) existing types with different +APIs, in particular with different interface implementations: ```carbon interface Printable { @@ -1835,60 +1833,11 @@ class FormattedSongByTitle { This allows developers to provide implementations of new interfaces (as in `SongByTitle`), provide different implementations of the same interface (as in `FormattedSong`), or mix and match implementations from other compatible types -(as in `FormattedSongByTitle`). The rules are: +(as in `FormattedSongByTitle`). -- You can add any declaration that you could add to a class except for - declarations that would change the representation of the type. This means - you can add methods, functions, interface implementations, and aliases, but - not fields, base classes, or virtual functions. The specific implementations - of virtual functions are part of the type representation, and so no virtual - functions may be overridden in an adapter either. -- The adapted type is compatible with the original type, and that relationship - is an equivalence class, so all of `Song`, `SongByTitle`, `FormattedSong`, - and `FormattedSongByTitle` end up compatible with each other. -- Since adapted types are compatible with the original type, you may - explicitly cast between them, but there is no implicit conversion between - these types. - -Inside an adapter, the `Self` type matches the adapter. Members of the original -type may be accessed either by a cast: - -```carbon -class SongByTitle { - adapt Song; - extend impl as Ordered { - fn Less(self, rhs: Self) -> bool { - return (self as Song).Title() < (rhs as Song).Title(); - } - } -} -``` - -or using a qualified member access expression: - -```carbon -class SongByTitle { - adapt Song; - extend impl as Ordered { - fn Less(self, rhs: Self) -> bool { - return self.(Song.Title)() < rhs.(Song.Title)(); - } - } -} -``` - -**Comparison with other languages:** This matches the Rust idiom called -"newtype", which is used to implement traits on types while avoiding -[coherence](terminology.md#coherence) problems, see -[here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) -and -[here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial). -Rust's mechanism doesn't directly support reusing implementations, though some -of that is provided by macros defined in libraries. Haskell has a -[`newtype` feature](https://wiki.haskell.org/Newtype) as well. Haskell's feature -doesn't directly support reusing implementations either, but the most popular -compiler provides it as -[an extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html). +For the definition of adapters, including what declarations can be added to an +adapter, compatibility rules, member access, and casting between adapted types, +see [adapters in the class design](/docs/design/classes.md#adapters). ### Adapter compatibility @@ -1952,7 +1901,7 @@ the API of the original type. The two most common cases expected are adding and replacing an interface implementation. Users would indicate that an adapter starts from the original type's existing API by using the `extend` keyword before `adapt`, which -[extends member access to lookup names in the adapted class](../expressions/member_access.md#extend) +[extends member access to lookup names in the adapted class](/docs/design/expressions/member_access.md#extend) along with `impl` lookup: ```carbon @@ -2216,21 +2165,6 @@ fn Render(w: Window) { } ``` -### Future work: Adapter with stricter invariants - -**Future work:** Rust also uses the newtype idiom to create types with -additional invariants or other information encoded in the type -([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html), -[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction), -[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)). -This is used to record in the type system that some data has passed validation -checks, like `ValidDate` with the same data layout as `Date`. Or to record the -units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet` -versus `Meters`. We should have some way of restricting the casts between a type -and an adapter to address this use case. One possibility would be to add the -keyword `private` before `adapt`, so you might write -`extend private adapt Date;`. - ## Associated constants > **TODO:** Update this section to reflect the new rules and guidance on @@ -2741,7 +2675,7 @@ member of another. The `where` operator is not associative, so a type expression using multiple must use round parens `(`...`)` to specify grouping. The scope of a facet type formed by a `where` declaration -[extends](../expressions/member_access.md#extend) the scope of its first +[extends](/docs/design/expressions/member_access.md#extend) the scope of its first operand, and the resulting facet type is complete if that scope it extends is complete. @@ -4007,7 +3941,7 @@ Given a type `U`, define the facet type `CompatibleWith(U)` as follows: > `CompatibleWith(U)` is a facet type whose values are facets `T` such that > `T as type` and `U as type` are -> [compatible types](terminology.md#compatible-types). That is values of `T` and +> [compatible types](/docs/design/classes.md#compatible-types). That is values of `T` and > `U` as types can be cast back and forth without any change in representation > (for example `T` is an [adapter](#adapting-types) for `U`). @@ -5475,7 +5409,7 @@ An incomplete `C` cannot be used in the following contexts: - ❌ `interface `...` { extend require impls C; }` or `constraint `...` { extend require impls C; }` - An `extend` declaration requires the target scope to be complete. See - [`extend` in member access](../expressions/member_access.md#extend). + [`extend` in member access](/docs/design/expressions/member_access.md#extend). - ❌ `T: C` ... `T impls A` where `T` is a checked binding, and `A` is an interface or named constraint different from `C` - Need to see the definition of `C` to see if it implies `A`. diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index b62ca3f7ab1e..675d9907dc3b 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -526,8 +526,8 @@ cast from `T` to `CDCover`. ### Adapting types -Carbon has a mechanism called [adapting types](terminology.md#adapting-a-type) -to create new types that are [compatible](terminology.md#compatible-types) with +Carbon has a mechanism called [adapting types](/docs/design/classes.md#adapters) +to create new types that are [compatible](/docs/design/classes.md#compatible-types) with existing types but with different interface implementations. This could be used to add or replace implementations, or define implementations for reuse. diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index d1f66cbd11b0..684a689f9920 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -38,10 +38,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Member access](#member-access) - [Simple member access](#simple-member-access) - [Qualified member access expression](#qualified-member-access-expression) -- [Compatible types](#compatible-types) - [Subtyping and casting](#subtyping-and-casting) - [Coherence](#coherence) -- [Adapting a type](#adapting-a-type) - [Type erasure](#type-erasure) - [Archetype](#archetype) - [Extending an interface](#extending-an-interface) @@ -478,7 +476,7 @@ members of the interface as named members of the type. This means that the members of the interface are available by way of both [simple member access and qualified member access expressions](#member-access). See -[how `extend` affects member access](../expressions/member_access.md#extend). +[how `extend` affects member access](/docs/design/expressions/member_access.md#extend). If a type implements an interface without extending, the members of the interface may only be accessed using @@ -522,22 +520,6 @@ member access expression `s1.(Comparable.Less)(s2)`. This form may be used to access any member of an interface implemented for a type, whether or not it [extends the implementation](#extending-an-impl). -## Compatible types - -Two types are compatible if they have the same notional set of values and -represent those values in the same way, even if they expose different APIs. The -representation of a type describes how the values of that type are represented -as a sequence of bits in memory. The set of values of a type includes properties -that the compiler can't directly see, such as invariants that the type -maintains. - -We can't just say two types are compatible based on structural reasons. Instead, -we have specific constructs that create compatible types from existing types in -ways that encourage preserving the programmer's intended semantics and -invariants, such as implementing the API of the new type by calling (public) -methods of the original API, instead of accessing any private implementation -details. - ## Subtyping and casting Both subtyping and casting are different names for changing the type of a value @@ -569,14 +551,14 @@ make it clear that the data representation of the value is not changing, just its type as reflected in the API available to manipulate the value. Casting is indicated explicitly by way of some syntax in the source code. You -might use a cast to switch between [type adaptations](#adapting-a-type), or to +might use a cast to switch between [type adaptations](/docs/design/classes.md#adapters), or to be explicit where an implicit conversion would otherwise occur. For now, we are saying "`x as y`" is the provisional syntax in Carbon for casting the value `x` to the type `y`. Note that outside of generics, the term "casting" includes any explicit type change, including those that change the data representation. In contexts where an expression of one type is provided and a different type is -required, an [implicit conversion](../expressions/implicit_conversions.md) is +required, an [implicit conversion](/docs/design/expressions/implicit_conversions.md) is performed if it is considered safe to do so. Such an implicit conversion, if permitted, always has the same meaning as an explicit cast. @@ -607,27 +589,6 @@ This is enforced using two kinds of rules: The rationale for Carbon choosing coherence and alternatives considered may be found in [this appendix](appendix-coherence.md) -## Adapting a type - -A type can be adapted by creating a new type that is -[compatible](#compatible-types) with an existing type, but has a different API. -In particular, the new type might implement different interfaces or provide -different implementations of the same interfaces. - -Unlike extending a type (as with C++ class inheritance), you are not allowed to -add new data fields onto the end of the representation -- you may only change -the API. This means that it is safe to [cast](#subtyping-and-casting) a value -between those two types without any dynamic checks or danger of -[object slicing](https://en.wikipedia.org/wiki/Object_slicing). - -This is called "newtype" in Rust, and is used for capturing additional -information in types to improve type safety by moving some checking to compile -time ([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html), -[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction), -[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)) -and as a workaround for -[Rust's orphan rules for coherence](https://github.com/Ixrec/rust-orphan-rules#why-are-the-orphan-rules-controversial). - ## Type erasure "Type erasure" is where a type's API is replaced by a subset. Everything outside @@ -664,7 +625,7 @@ An interface can be extended by defining an interface that includes the full API of another interface, plus some additional API. Types implementing the extended interface should automatically be considered to have implemented the narrower interface. See -[how `extend` affects member access](../expressions/member_access.md#extend). +[how `extend` affects member access](/docs/design/expressions/member_access.md#extend). ## Dynamic-dispatch witness table @@ -863,7 +824,7 @@ express, for example: element type. - An interface may define an associated facet that needs to be constrained to implement some interfaces. -- This type must be [compatible](#compatible-types) with another type. You +- This type must be [compatible](/docs/design/classes.md#compatible-types) with another type. You might use this to define alternate implementations of a single interfaces, such as sorting order, for a single type. diff --git a/docs/design/values.md b/docs/design/values.md index 9eda5584a578..42f9fe744c42 100644 --- a/docs/design/values.md +++ b/docs/design/values.md @@ -991,7 +991,7 @@ Each of the conversions described in this section is explicit if and only if it invokes another explicit type conversion. Otherwise, it is implicit. A type conversion of an expression with primitive extended type to a -[compatible type](generics/terminology.md#compatible-types) just re-interprets +[compatible type](classes.md#compatible-types) just re-interprets the expression's result with a new type, so it requires no run-time work, and has the same category as the input expression. diff --git a/proposals/p003720-member-binding-operators.md b/proposals/p003720-member-binding-operators.md index 26a6acbf1956..a6bae64ecfcd 100644 --- a/proposals/p003720-member-binding-operators.md +++ b/proposals/p003720-member-binding-operators.md @@ -187,8 +187,8 @@ class C { Each member of `C` with a distinct name will have a corresponding type (like `__TypeOf_C_F`) and value of that type (like `__C_F`). There are two more types for each member function (either static class function or method), though, that -[adapt](/docs/design/generics/terminology.md#adapting-a-type) `C` and represent -the type of binding that member with either a `C` value or variable. +[adapt](/docs/design/classes.md#adapters) `C` and represent the type of binding +that member with either a `C` value or variable. ```carbon class __TypeOf_C_F {}