mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
This PR corrects misspellings identified by the [check-spelling action](https://github.com/marketplace/actions/check-spelling). The misspellings have been reported at https://github.com/jsoref/carbon-lang/commit/38a1c1640151899fd6da0442a92557f9543b6280#commitcomment-79197316 The action reports that the changes in this PR would make it happy: https://github.com/jsoref/carbon-lang/commit/173c8f9083a68aa61f7cfe94f720f1e5dc7f1ea3 Note: this PR does not include the action. If you're interested in running a spell check on every PR and push, that can be offered separately. Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
284 lines
12 KiB
Markdown
284 lines
12 KiB
Markdown
# Nominal classes and methods
|
|
|
|
<!--
|
|
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/722)
|
|
|
|
<!-- 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)
|
|
- [Method syntax](#method-syntax)
|
|
- [Rationale](#rationale)
|
|
- [Full receiver type](#full-receiver-type)
|
|
- [Method names line up](#method-names-line-up)
|
|
- [Receiver in square brackets](#receiver-in-square-brackets)
|
|
- [Receiver parameter is named `me`](#receiver-parameter-is-named-me)
|
|
- [Keyword to indicate pass by address](#keyword-to-indicate-pass-by-address)
|
|
- [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site)
|
|
- [Differences between functions and methods](#differences-between-functions-and-methods)
|
|
- [Specifying linkage as part of the access modifier](#specifying-linkage-as-part-of-the-access-modifier)
|
|
- [Nominal data class](#nominal-data-class)
|
|
- [Let constants](#let-constants)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Problem
|
|
|
|
We need facilities for defining new nominal
|
|
[record types](<https://en.wikipedia.org/wiki/Record_(computer_science)>) in
|
|
Carbon. They will need to support object-oriented features such as methods.
|
|
|
|
## Background
|
|
|
|
This is a follow up to
|
|
[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561)
|
|
which laid a foundation.
|
|
|
|
## Proposal
|
|
|
|
The proposal is to update [docs/design/classes.md](/docs/design/classes.md) as
|
|
described in [this PR](https://github.com/carbon-language/carbon-lang/pull/701).
|
|
|
|
## Rationale based on Carbon's goals
|
|
|
|
This particular proposal is focusing on
|
|
[the Carbon goal](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
|
that code is "easy to read, understand, and write." Future proposals will
|
|
address other aspects of the class type design such as performance.
|
|
|
|
## Alternatives considered
|
|
|
|
### Method syntax
|
|
|
|
The proposed method syntax was decided in question-for-leads issue
|
|
[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494).
|
|
|
|
A wide variety of alternatives were considered:
|
|
|
|
- Using a different introducer such as `method` to distinguish methods from
|
|
functions.
|
|
- Putting a pattern to match the receiver before the method name.
|
|
- Omitting either the receiver name or the receiver type.
|
|
- Allowing the user to specify the receiver name instead of using a parameter
|
|
named `me` to indicate it is a method.
|
|
- Using syntax between `fn` and the method name to indicate that it is a
|
|
method, and whether the method takes a value or the address of the receiver.
|
|
- Using a keyword to indicate that the first parameter is the receiver, as in
|
|
[C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html).
|
|
- Using a delimiter in the parameter list after the receiver type, but the
|
|
main choice of `;` seemed pretty subtle.
|
|
- Putting the receiver pattern in a separate set of brackets. This could be an
|
|
additional set of parens `(`...`)` before the explicit parameter list. Or
|
|
the deduced parameters could be put in angle brackets `<`...`>` and the
|
|
receiver in square brackets `[`...`]`.
|
|
|
|
Examples:
|
|
|
|
```
|
|
method (this: Self*) Set(n: Int);
|
|
fn [Me*].Set(n: Int);
|
|
fn ->Set(n: Int);
|
|
fn& Set(n: Int);
|
|
fn &Set(s: Self*, n: Int);
|
|
fn Set(this self: Self*, n: Int);
|
|
fn Set[this self: Self*](n: Int);
|
|
fn Set(self: Self*; n: Int);
|
|
fn Set(self: Self*)(n: Int);
|
|
```
|
|
|
|
Note: These examples are written using the parameter syntax decided in
|
|
[issue #542](https://github.com/carbon-language/carbon-lang/issues/542), though
|
|
that does not match the syntax used in the discussion at the time.
|
|
|
|
#### Rationale
|
|
|
|
##### Full receiver type
|
|
|
|
We wanted the option to specify the type of the receiver. In C++, the type of
|
|
this is controlled by way of special syntax, such as putting a `const` keyword
|
|
at the end of the method declaration. We wanted to treat the receiver type more
|
|
consistently with other parameter types, like is being considered in
|
|
[C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html).
|
|
That proposal also showed the value of including the entire type, rather than
|
|
just part such as whether the parameter would be passed by value or address. We
|
|
already plan to use this feature to conditionally include methods based on
|
|
whether parameters to the type satisfy additional constraints. In this example,
|
|
`FixedArray(T, N)` has a `Print()` method if `T` is `Printable`:
|
|
|
|
```
|
|
class FixedArray(T:! Type, N:! Int) {
|
|
// ...
|
|
fn Print[me: FixedArray(P:! Printable, N)]() { ... }
|
|
}
|
|
```
|
|
|
|
We did consider the option of making the type of the receiver optional. This is
|
|
not what we decided to try first, but is an idea we would consider in the
|
|
future.
|
|
|
|
##### Method names line up
|
|
|
|
Using the same introducer `fn` as functions, as decided in
|
|
[issue #463](https://github.com/carbon-language/carbon-lang/issues/463), along
|
|
with the method name immediately afterwards means that function and method names
|
|
line up in the same column visually.
|
|
|
|
```
|
|
struct IntContainer {
|
|
// Non-methods for building instances
|
|
fn MakeFromInts ... -> IntContainer;
|
|
fn MakeRepeating ... -> IntContainer;
|
|
|
|
// Methods
|
|
fn Size ... -> Int;
|
|
fn First ... -> Int;
|
|
|
|
fn Clear ...;
|
|
fn Append ...;
|
|
}
|
|
```
|
|
|
|
This is to ease scanning and to put the most important information up front.
|
|
|
|
We also considered using another 2-character introducer that would distinguish
|
|
methods from associated functions, like `me`, but it did not garner much
|
|
support. The name `me` was not evocative enough of "method", and people
|
|
generally preferred matching other function declarations. We also considered
|
|
using different introducers to communicate whether the receiver was passed by
|
|
value or by address, for example `ro` for "read-only" and `rw` for "read-write".
|
|
|
|
##### Receiver in square brackets
|
|
|
|
Putting the receiver pattern inside the explicit parameter list, as is done in
|
|
Python, would lead to an unfortunate mismatch between the arguments listed at
|
|
the call and the function declaration. That motivated putting the receiver
|
|
pattern inside square brackets (`[`...`]`) with deduced parameters. There were
|
|
concerns, however, that this parameter did not act like deduced parameters.
|
|
|
|
##### Receiver parameter is named `me`
|
|
|
|
Having the receiver name be fixed is helpful for consistency. This benefits
|
|
readers, and simplifies copying or moving code between functions. A fixed
|
|
receiver name also allows us to identify the receiver pattern in the function
|
|
declaration, and identify it as a method instead of an ordinary function
|
|
associated with the type, like
|
|
[static methods in C++](<https://en.wikipedia.org/wiki/Static_(keyword)#Static_method>).
|
|
|
|
Finally, we wanted the bodies of methods to access members of the object using
|
|
an explicit member access. This means we need the name used to access members to
|
|
be short, and so we chose the name `me`.
|
|
|
|
##### Keyword to indicate pass by address
|
|
|
|
We considered using just whether the receiver type was a pointer type to
|
|
indicate whether the receiver was passed by address, but we preferred type
|
|
information to flow in one direction. Otherwise this would cause a problem if we
|
|
want to allow _deduction_ of the object parameter type. This deduction can't be
|
|
used to select between pointer and not pointer.
|
|
|
|
We also considered supporting reference types that could bind to lvalues without
|
|
explicitly taking the address of the object, but references would have added a
|
|
lot of complexity to the type system and we believed that they are not otherwise
|
|
necessary.
|
|
|
|
We decided to adopt an approach where the parameter declaration can contain a
|
|
marker for matching an argument's
|
|
[value category](https://en.cppreference.com/w/cpp/language/value_category)
|
|
separately from its type. This marker would be present for methods that mutate
|
|
the object, and so requires that the receiver be an lvalue to match the pattern.
|
|
The marker means "first take the address of the argument and then match the rest
|
|
of the pattern." We considered a few different possible ways to write this:
|
|
|
|
```
|
|
fn Set[&me: Self*](n: Int);
|
|
fn Set[*(me: Self*)](n: Int);
|
|
fn Set[ref me: Self*](n: Int);
|
|
```
|
|
|
|
We eventually settled on using a keyword `addr`.
|
|
|
|
```
|
|
fn Set[addr me: Self*](n: Int);
|
|
```
|
|
|
|
This doesn't use up a symbol, makes it easier to find in search engines, and
|
|
allows us to add more keywords in the future for other calling conventions, such
|
|
as `inout`. We may even find that those other keywords have preferable semantics
|
|
so we may eventually drop `addr`.
|
|
|
|
This keyword approach also is consistent with how we are marking template
|
|
parameters with the `template` keyword, as decided in
|
|
[issue #565 on generic syntax](https://github.com/carbon-language/carbon-lang/issues/565).
|
|
|
|
### Marking mutating methods at the call site
|
|
|
|
We considered making it visible at the call site when the receiver was passed by
|
|
address, with the address-of operator `&`.
|
|
|
|
```
|
|
(&x).Set(4);
|
|
```
|
|
|
|
This would in effect make the mutating methods be methods on the pointer type
|
|
rather than the class type.
|
|
|
|
### Differences between functions and methods
|
|
|
|
Question-for-leads issue
|
|
[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494)
|
|
also considered what would be different between functions and methods:
|
|
|
|
- Methods use a different calling syntax: `x.F(n)`.
|
|
- Methods distinguish between taking the receiver (`x`) by value or by
|
|
pointer, without changing the call syntax.
|
|
- Methods have to be declared in the body of the class.
|
|
- Methods and associated functions both have access to private members of the
|
|
class.
|
|
- Only methods can opt in to using dynamic dispatch.
|
|
- The receiver parameter to a method varies covariantly in inheritance unlike
|
|
other parameter types.
|
|
|
|
### Specifying linkage as part of the access modifier
|
|
|
|
We considered various access modifiers such as `internal` or `private.internal`
|
|
that would enforce internal linkage in addition to restricted visibility. We
|
|
decided to postpone such considerations for the time being.
|
|
|
|
Even if we _do_ need explicit controls here, we did not feel the need to
|
|
front-load that complexity right now and with relatively less time or experience
|
|
to evaluate the tradeoffs. If and when linkage becomes a visible issue and
|
|
important to discuss, we can add it. Until then, we can see how much mileage we
|
|
can get out of purely implementation techniques.
|
|
|
|
### Nominal data class
|
|
|
|
We needed some way of opting a nominal class into the fieldwise behavior of a
|
|
data class. [In Kotlin](https://kotlinlang.org/docs/data-classes.html), you
|
|
precede the `class` declaration with a `data` keyword. Carbon already has a way
|
|
of marking types as having specific semantic properties, implementing
|
|
interfaces. This leverages the support for interfaces in the language to be able
|
|
to express things like "here is a blanket implementation of an interface for all
|
|
data classes" in a consistent way.
|
|
|
|
We chose the name `Data` rather than `DataClass` for the interface since tuples
|
|
implicitly implement the interface and were "product types" rather than
|
|
"classes".
|
|
|
|
### Let constants
|
|
|
|
We wanted a consistent interpretation for `let` declarations in classes and
|
|
function bodies. Experience from C++ `const int` variables, where we try
|
|
constant-evaluation and then give the program different semantics based on
|
|
whether such evaluation happened to work, suggested we wanted to instead require
|
|
`:!` in all places where a value that's usable during compilation is introduced.
|