Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch generics to using compound/simple member access terminology #1138

Merged
merged 3 commits into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 39 additions & 32 deletions docs/design/generics/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Implementing interfaces](#implementing-interfaces)
- [Implementing multiple interfaces](#implementing-multiple-interfaces)
- [External impl](#external-impl)
- [Qualified member names](#qualified-member-names)
- [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access)
- [Access](#access)
- [Generics](#generics)
- [Return type](#return-type)
Expand Down Expand Up @@ -188,9 +188,9 @@ This includes members of `ConvertibleToString` that are not explicitly named in
the `impl` definition but have defaults. Whether the implementation is defined
as [internal](terminology.md#internal-impl) or
[external](terminology.md#external-impl), you may access the `ToString` function
for a `Song` value `s` by writing a
[qualified](terminology.md#qualified-and-unqualified-member-names) function
call, like `s.(ConvertibleToString.ToString)()`.
for a `Song` value `s` by a writing function call
[using the compound member access syntax with the qualified name](terminology.md#compound-member-access-using-qualified-names),
like `s.(ConvertibleToString.ToString)()`.

If `Song` doesn't implement an interface or we would like to use a different
implementation of that interface, we can define another type that also has the
Expand Down Expand Up @@ -405,8 +405,9 @@ Carbon requires `impl`s defined in a different library to be `external` so that
the API of `Point3` doesn't change based on what is imported. It would be
particularly bad if two different libraries implemented interfaces with
conflicting names that both affected the API of a single type. As a consequence
of this restriction, you can find all the names of direct (unqualified) members
of a type in the definition of that type. The only thing that may be in another
of this restriction, you can find all the names of direct members (those
available by [simple member access](terminology.md#simple-member-access)) of a
type in the definition of that type. The only thing that may be in another
library is an `impl` of an interface.

You might also use `external impl` to implement an interface for a type to avoid
Expand Down Expand Up @@ -476,8 +477,10 @@ same library as the class?
different files based on explicit configuration in that file. For example, we
could support a declaration that a given interface or a given method of an
interface is "in scope" for a particular type in this file. With that
declaration, the method could be called unqualified. This avoids most concerns
arising from name collisions between interfaces. It has a few downsides though:
declaration, the method could be called using
[simple member access](terminology.md#simple-member-access). This avoids most
concerns arising from name collisions between interfaces. It has a few downsides
though:

- It increases variability between files, since the same type will have
different APIs depending on these declarations. This makes it harder to
Expand All @@ -495,12 +498,14 @@ Swift and Rust, we don't allow a type's API to be modified outside its
definition. So in Carbon a type's API is consistent no matter what is imported,
unlike Swift and Rust.

### Qualified member names
### Qualified member names and compound member access

Given a value of type `Point3` and an interface `Vector` implemented for that
type, you can access the methods from that interface using the member's
_qualified name_, whether or not the implementation is done externally with an
`external impl` declaration:
_qualified name_ using
[the compound member access syntax](terminology.md#simple-member-access),
whether or not the implementation is done externally with an `external impl`
declaration:

```
var p1: Point3 = {.x = 1.0, .y = 2.0};
Expand Down Expand Up @@ -581,7 +586,9 @@ present in the signature of the function to type check the body of

Names are looked up in the body of `AddAndScaleGeneric` for values of type `T`
in `Vector`. This means that `AddAndScaleGeneric` is interpreted as equivalent
to adding a `Vector` qualification to all unqualified member accesses of `T`:
to adding a `Vector`
[qualification](#qualified-member-names-and-compound-member-access) to replace
all simple member accesses of `T`:

```
fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: Double) -> T {
Expand Down Expand Up @@ -785,7 +792,8 @@ signatures. Implementing `Vector` would not imply an implementation of
An interface's name may be used in a few different contexts:

- to define [an `impl` for a type](#implementing-interfaces),
- as a namespace name in [a qualified name](#qualified-member-names), and
- as a namespace name in
[a qualified name](#qualified-member-names-and-compound-member-access), and
- as a [type-of-type](terminology.md#type-of-type) for
[a generic type parameter](#generics).

Expand All @@ -806,7 +814,8 @@ aliases for the corresponding qualified names inside `Vector` as a namespace.

The requirements determine which types are values of a given type-of-type. The
set of names in a type-of-type determines the API of a generic type value and
define the result of qualified member name lookup.
define the result of [member access](/docs/design/expressions/member_access.md)
into the type-of-type.

This general structure of type-of-types holds not just for interfaces, but
others described in the rest of this document.
Expand Down Expand Up @@ -875,9 +884,8 @@ members of those interfaces.
To declare a named constraint that includes other declarations for use with
template parameters, use the `template` keyword before `constraint`. Method,
associated type, and associated function requirements may only be declared
inside a `template constraint`. Note that a generic constraint ignores the
unqualified member names defined for a type, but a template constraint can
depend on them.
inside a `template constraint`. Note that a generic constraint ignores the names
of members defined for a type, but a template constraint can depend on them.

There is an analogy between declarations used in a `constraint` and in an
`interface` definition. If an `interface` `I` has (non-`alias`) declarations
Expand Down Expand Up @@ -1036,8 +1044,8 @@ constraint {
```

Conflicts can be resolved at the call site using
[the qualified name syntax](#qualified-member-names), or by defining a named
constraint explicitly and renaming the methods:
[the compound member access syntax using qualified names](#qualified-member-names-and-compound-member-access),
or by defining a named constraint explicitly and renaming the methods:

```
constraint RenderableAndEndOfGame {
Expand Down Expand Up @@ -1561,7 +1569,7 @@ adapter SongByTitle for Song {
}
```

or using qualified names:
or using qualified names with the compound member access syntax:

```
adapter SongByTitle for Song {
Expand Down Expand Up @@ -1852,8 +1860,8 @@ external impl Window as DrawingContext { ... }
An adapter can make that much more convenient by making a compatible type where
the interface is [implemented internally](terminology.md#internal-impl). This
avoids having to
[qualify](terminology.md#qualified-and-unqualified-member-names) each call to
methods in the interface.
[qualify](terminology.md#compound-member-access-using-qualified-names) each call
to methods in the interface.

```
adapter DrawInWindow for Window {
Expand Down Expand Up @@ -2580,14 +2588,13 @@ fn Contains
the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well.
However, inside the body of `Contains`, `CT.ElementType` will only act like the
implementation of `Comparable` is [external](#external-impl). That is, items
from the `needles` container won't have an unqualified `Compare` method member,
but can still be implicitly converted to `Comparable` and can still call
`Compare` using the qualified member syntax, `needle.(Comparable.Compare)(elt)`.
The rule is that an `==` `where` constraint between two type variables does not
modify the set of unqualified member names of either type. (If you write
from the `needles` container won't directly have a `Compare` method member, but
can still be implicitly converted to `Comparable` and can still call `Compare`
using the compound member access syntax, `needle.(Comparable.Compare)(elt)`. The
rule is that an `==` `where` constraint between two type variables does not
modify the set of member names of either type. (If you write
`where .ElementType = String` with a `=` and a concrete type, then
`.ElementType` is actually set to `String` including the complete unqualified
`String` API.)
`.ElementType` is actually set to `String` including the complete `String` API.)

Note that `==` constraints are symmetric, so the previous declaration of
`Contains` is equivalent to an alternative declaration where `CT` is declared
Expand Down Expand Up @@ -2876,9 +2883,9 @@ This implied constraint is equivalent to the explicit constraint that each
parameter and return type [is legal](#must-be-legal-type-argument-constraints).

**Note:** These implied constraints affect the _requirements_ of a generic type
parameter, but not its _unqualified member names_. This way you can always look
at the declaration to see how name resolution works, without having to look up
the definitions of everything it is used as an argument to.
parameter, but not its _member names_. This way you can always look at the
declaration to see how name resolution works, without having to look up the
definitions of everything it is used as an argument to.

**Limitation:** To limit readability concerns and ambiguity, this feature is
limited to a single signature. Consider this interface declaration:
Expand Down
41 changes: 22 additions & 19 deletions docs/design/generics/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pointers to other design documents that dive deeper into individual topics.
- [Defining interfaces](#defining-interfaces)
- [Contrast with templates](#contrast-with-templates)
- [Implementing interfaces](#implementing-interfaces)
- [Qualified and unqualified access](#qualified-and-unqualified-access)
- [Accessing members of interfaces](#accessing-members-of-interfaces)
- [Type-of-types](#type-of-types)
- [Generic functions](#generic-functions)
- [Deduced parameters](#deduced-parameters)
Expand Down Expand Up @@ -90,8 +90,9 @@ Summary of how Carbon generics work:
["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.
- Alternatively, you may resolve name conflicts by using the compound member
access syntax to directly call a function from a specific interface using a
qualified name.

## What are generics?

Expand Down Expand Up @@ -216,16 +217,17 @@ external impl Song as Comparable {

Implementations may be defined within the class definition itself or
out-of-line. Implementations may optionally be start with the `external` keyword
to say the members of the interface are not unqualified members of the class.
Out-of-line implementations must be external. External implementations may be
defined in the library defining either the class or the interface.
to say the members of the interface are not members of the class. Out-of-line
implementations must be external. External implementations may be defined in the
library defining either the class or the interface.

#### Qualified and unqualified access
#### Accessing members of interfaces

The methods of an interface implemented internally within the class definition
may be called with the ordinary unqualified member syntax. Methods of all
implemented interfaces may be called with the
[qualified member syntax](terminology.md#qualified-and-unqualified-member-names),
may be called with the
[simple member access syntax](terminology.md#simple-member-access). Methods of
all implemented interfaces may be called with the
[compound member access syntax using qualified names](terminology.md#compound-member-access-using-qualified-names),
whether they are defined internally or externally.

```
Expand All @@ -235,7 +237,8 @@ song.Print();
// `Less` is defined in `Comparable`, which is implemented
// externally for `Song`
song.(Comparable.Less)(song);
// Can also call `Print` using the qualified syntax:
// Can also call `Print` using the compound access syntax,
// using the qualified name `Printable.Print`:
song.(Printable.Print)();
```

Expand All @@ -257,9 +260,8 @@ 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. Those names are used for
[unqualfied member lookup](terminology.md#qualified-and-unqualified-member-names)
in scopes where the value of the type is not known, such as when the type is a
generic parameter.
[simple member lookup](terminology.md#simple-member-access) in scopes where the
value of the type is not known, such as when the type is a generic parameter.

You may combine interfaces into new type-of-types using
[the `&` operator](#combining-interfaces) or
Expand Down Expand Up @@ -325,8 +327,9 @@ differently because they are defined as generic, as long as you only refer to
the names defined by [type-of-type](#type-of-types) for the type parameter.

You may also refer to any of the methods of interfaces required by the
type-of-type using the [qualified syntax](#qualified-and-unqualified-access), as
shown in the following sections.
type-of-type using the
[compound member access syntax with the qualified member name](#accessing-members-of-interfaces),
as shown in the following sections.

A function can have a mix of generic, template, and regular parameters.
Likewise, it's allowed to pass a template or generic value to a generic or
Expand Down Expand Up @@ -409,7 +412,7 @@ fn F[T:! Renderable & EndOfGame](game_state: T*) -> (i32, i32) {
```

Names with conflicts can be accessed using the
[qualified syntax](#qualified-and-unqualified-access).
[compound member access syntax](#accessing-members-of-interfaces).

```
fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) {
Expand Down Expand Up @@ -442,8 +445,8 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) {
game_state->Draw_EndOfGame();
}
game_state->Draw_Renderable();
// Can still use qualified syntax for names
// not defined in the named constraint
// Can still use compound member access syntax for
// names not defined in the named constraint
return game_state->(Renderable.Center)();
}
```
Expand Down
51 changes: 38 additions & 13 deletions docs/design/generics/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Impls: Implementations of interfaces](#impls-implementations-of-interfaces)
- [Internal impl](#internal-impl)
- [External impl](#external-impl)
- [Qualified and unqualified member names](#qualified-and-unqualified-member-names)
- [Member access](#member-access)
- [Simple member access](#simple-member-access)
- [Compound member access using qualified names](#compound-member-access-using-qualified-names)
- [Compatible types](#compatible-types)
- [Subtyping and casting](#subtyping-and-casting)
- [Coherence](#coherence)
Expand Down Expand Up @@ -357,28 +359,51 @@ constraint as a way to implement all of the interfaces it requires.

A type that implements an interface _internally_ has all the named members of
the interface as named members of the type. This means that the members of the
interface may be accessed as either
[unqualified or qualified members](#qualified-and-unqualified-member-names).
interface are available by way of both
[simple member access and compound member access using qualified names](#member-access).

### External impl

In contrast, a type that implements an interface _externally_ does not include
the named members of the interface in the type. The members of the interface are
still implemented by the type, though, and so may be accessed using the
[qualified names](#qualified-and-unqualified-member-names) of those members.
still implemented by the type, though, and so may be accessed using
[compound member access using the qualified names](#compound-member-access-using-qualified-names)
of those members.

## Qualified and unqualified member names
## Member access

A qualified member includes both the name of the interface defining the member
and the name of the member. So if `String` implements `Comparable` which has a
`Less` method, and `s1` and `s2` are variables of type `String`, then the `Less`
There are two different kinds of member access: _simple_ and _compound_. There
is a [member access design document](/docs/design/expressions/member_access.md)
with the details, but a summary of how they apply to generics is given here.

### Simple member access

Simple member access has the from `object.member`, where `member` is a word
naming a member of `object`. This form may be used to access members of
interfaces [implemented internally](#internal-impl) by the type of `object`.

If `String` implements `Printable` internally, then `s1.Print()` calls the
`Print` method of `Printable` using simple member access. In this case, the name
`Print` is used without qualifying it with the name of the interface it is a
member of since it is recognized as a member of the type itself as well.

### Compound member access using qualified names

Compound member access has the form `object.(expression)`, where `expression` is
resolved in the containing scope. This expression may be the _qualified member
name_ of an interface member, that consists of the name of the interface,
possibly qualified with a package or namespace name, a dot `.` and the name of
the member.

For example, if the `Comparable` interface has a `Less` member method, then the
qualified name of that member is `Comparable.Less`. So if `String` implements
`Comparable`, and `s1` and `s2` are variables of type `String`, then the `Less`
method may be called using the qualified member name by writing
`s1.(Comparable.Less)(s2)`.

If the interface is implemented internally, then the method can be called using
the unqualified syntax as well. If `String` implements `Printable` internally,
then `s1.Print()` calls the `Print` method of `Printable` as an unqualified
member.
This form may be used to access any member of an interface implemented for a
type, whether it is implemented [internally](#internal-impl) or
[externally](#external-impl).

## Compatible types

Expand Down