mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 18:50:09 +01:00
Types are values of type type (#2360)
Define a "type" to be a value of type `type`. Contexts expecting a type perform an implicit conversion to `type`. Values like `()` and `(i32, i32)` and `{}` are no longer types, but instead implicitly convert to `type`. Values of interface or constraint type (now called "facets") are similarly not formally types but implicitly convert to `type`.
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Co-authored-by: josh11b <josh11b@users.noreply.github.com>
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
This commit is contained in:
co-authored by
Chandler Carruth
josh11b
Jon Ross-Perkins
parent
026c4b9dc3
commit
16dcdc2a34
@@ -0,0 +1,650 @@
|
||||
# Types are values of type `type`
|
||||
|
||||
<!--
|
||||
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/2360)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Tuples behave inconsistently](#tuples-behave-inconsistently)
|
||||
- [Qualified access to constraint members is inconsistent](#qualified-access-to-constraint-members-is-inconsistent)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Details](#details)
|
||||
- [Terminology](#terminology)
|
||||
- [`type` and `Core.Type`](#type-and-coretype)
|
||||
- [Compound member access](#compound-member-access)
|
||||
- [Instance binding](#instance-binding)
|
||||
- [Impl lookup](#impl-lookup)
|
||||
- [Examples](#examples)
|
||||
- [Tuple and struct literals](#tuple-and-struct-literals)
|
||||
- [Name lookup into values with constrained types](#name-lookup-into-values-with-constrained-types)
|
||||
- [Types in diagnostics](#types-in-diagnostics)
|
||||
- [Rationale](#rationale)
|
||||
- [Teachability](#teachability)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [Alternative member access rules](#alternative-member-access-rules)
|
||||
- [Alternative terminology](#alternative-terminology)
|
||||
- [`Core.Type` is an empty named constraint](#coretype-is-an-empty-named-constraint)
|
||||
- [Write tuple types differently than tuples](#write-tuple-types-differently-than-tuples)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
Define a "type" to be a value of type `type`. Contexts expecting a type perform
|
||||
an implicit conversion to `type`. Values like `()`, `(i32, i32)`, and `{}` are
|
||||
no longer types, but instead implicitly convert to `type`. Values of interface
|
||||
or constraint type (now called "facets") are similarly not formally types but
|
||||
implicitly convert to `type`.
|
||||
|
||||
The practical impact to the language semantics is relatively small. This
|
||||
proposal primarily establishes clearer, more principled terminology and design
|
||||
concepts with a few narrow improvements to the design's consistency.
|
||||
|
||||
## Problem
|
||||
|
||||
### Tuples behave inconsistently
|
||||
|
||||
Currently, the type of `(i32, i32)` is `(Type, Type)`, and similarly the type of
|
||||
`(Type, Type)` is also `(Type, Type)`. This leads to an inconsistency: for most
|
||||
values, asking for the type, then the type-of-type, and so on, quickly leads to
|
||||
`Type`, whose type is `Type`. But for a few values, notably tuples and empty
|
||||
structs, we instead bottom out at a different value.
|
||||
|
||||
This leads to implementation complexity and a lack of clarity around what is or
|
||||
is not a type. It is easy to find examples where explorer misbehaves or crashes
|
||||
due to getting tuple values and types mixed up.
|
||||
|
||||
Moreover, this inconsistency is limited to a few built-in types. There is no way
|
||||
to design a class type so it behaves like a tuple type.
|
||||
|
||||
### Qualified access to constraint members is inconsistent
|
||||
|
||||
Given the expression `x.(Interface.Function)`, where the function is an instance
|
||||
method, the behavior depends on whether `x` is a type.
|
||||
|
||||
- If `x` is not a type, but a value of type `T`, this requires
|
||||
`T is Interface` and looks for `Function` in the corresponding `impl`.
|
||||
Instance binding is performed.
|
||||
- If `x` is a type, this requires `x` to have symbolic value phase and
|
||||
requires that `x is Interface`. The result is an unbound member name.
|
||||
|
||||
This dual meaning has some problematic consequences:
|
||||
|
||||
- Even if `Interface.Function` is non-dependent, if `x` depends on a template
|
||||
parameter then we must treat the compound member access as being dependent.
|
||||
Qualification cannot be used to avoid dependence in templates.
|
||||
- Worse, if `x` is a template parameter, the interpretation will differ
|
||||
between instantiations, meaning that a template and a checked generic will
|
||||
have a discontinuity in behavior when `x` happens to be a type.
|
||||
- While it is possible to `impl Type as ...`, this rule means it is not
|
||||
possible to directly invoke methods that have been implemented this way. For
|
||||
example, given an `impl Type as DebugPrintable`,
|
||||
`i32.(DebugPrintable.Print)()` will look for `i32 as DebugPrintable` not
|
||||
`Type as DebugPrintable`.
|
||||
|
||||
## Background
|
||||
|
||||
- [#989: Member access expressions](/proposals/p0989.md) introduced the
|
||||
current compound member access rules.
|
||||
- [Issue #495](https://github.com/carbon-language/carbon-lang/issues/495)
|
||||
suggested adding an implicit conversion from tuples of types to type `Type`.
|
||||
- [Issue #508](https://github.com/carbon-language/carbon-lang/issues/508)
|
||||
raised the question of whether we want tuples like `((), (), ())` to be
|
||||
self-typed.
|
||||
- [Issue #2113](https://github.com/carbon-language/carbon-lang/issues/2113)
|
||||
gave us spelling and behavior guidelines for predefined names like `type`.
|
||||
|
||||
## Proposal
|
||||
|
||||
Instead of treating type-like values as being types regardless of the type of
|
||||
those values, we define a _type_ as being a value of type `type`.
|
||||
|
||||
This has the following consequences:
|
||||
|
||||
- `()` is a value of type `() as type`, not a value of type `()` (because the
|
||||
latter is not a type).
|
||||
- `() as type` is a value of type `type`.
|
||||
- Similarly, `(i32, i32)` is of type `(type, type) as type`, which is of
|
||||
type `type`.
|
||||
- Similarly, `{}` is of type `{} as type`, which is of type `type`.
|
||||
- More generally, _the type of any type is `type`_.
|
||||
- Given `T:! Hashable`, `T` is formally not a type, because it is not of type
|
||||
`type`. Indeed, member lookup into `type`, and hence into types, finds
|
||||
nothing, but member lookup into `T` finds members of `Hashable`.
|
||||
- Contexts in which a type is required, such as on the right hand side of a
|
||||
`:` or `:!` binding or the return type of a function, perform an implicit
|
||||
conversion to type `type`.
|
||||
- This means that a user-defined type can implement `ImplicitAs(type)`,
|
||||
annotated in whatever way we eventually decide to annotate functions
|
||||
that are usable at compile time, and values of that type will be usable
|
||||
as types, in the same way that `()` and `{}` are.
|
||||
- There are no singleton type-of-types. It's still possible to have, for
|
||||
example, the type of `i32` be a type whose only value is `i32`. The value
|
||||
`i32` would then not formally be a type, as its type would not be `type`,
|
||||
but it could still implicitly convert to `type`, as `()` does, if we so
|
||||
desire.
|
||||
- This extends to other cases: we could say that for integer literals,
|
||||
which we have recently been describing as having type `IntLiteral(n)`,
|
||||
there is an `impl IntLiteral(n) as ImplicitAs(type)` which converts a
|
||||
value of type `IntLiteral(n)` to the `type` value `IntLiteral(n)`. This
|
||||
would then permit `let v: 0 = 0;`.
|
||||
- Such changes to `i32` or integer literals are not part of this proposal.
|
||||
|
||||
## Details
|
||||
|
||||
### Terminology
|
||||
|
||||
The changes to the language rules in this proposal are fairly small, but there
|
||||
is a significant change in how we describe those rules. We now use the following
|
||||
terminology:
|
||||
|
||||
- A _type_ is a value of type `type`.
|
||||
- A _facet type_ is a type whose values are some subset of the values of
|
||||
`type`, determined by a set of constraints.
|
||||
|
||||
- Interface types and named constraint types are facet types whose
|
||||
constraints are that the interface or named constraint is satisfied by
|
||||
the type.
|
||||
- The values produced by `&` operations between facet types and by `where`
|
||||
expressions are facet types, whose set of constraints are determined by
|
||||
the `&` or `where` expression.
|
||||
- `type` is a facet type whose set of constraints is empty.
|
||||
|
||||
We previously referred to a facet type as a type-of-type, but that is no
|
||||
longer accurate, as the type of any type is now by definition `type`.
|
||||
|
||||
- A _facet_ is a value of a facet type. For example, `i32 as Hashable` is a
|
||||
facet, and `Hashable` is a facet type. Note that all types are facets.
|
||||
- A facet value can be thought of as a tuple of a possibly-symbolic
|
||||
identity for a type, plus any witnesses needed to demonstrate that the
|
||||
type satisfies the constraints of the facet type. A type is then a facet
|
||||
value that has no witnesses.
|
||||
- We use the term _generic type_ to refer to a type or facet introduced by a
|
||||
`:!` binding, such as in a generic parameter or associated constant.
|
||||
- This gives rise to terms such as _generic type parameter_ and
|
||||
_associated generic type_.
|
||||
- It is worth noting that a generic type is not necessarily a type. It may
|
||||
be a facet, and its type may be a facet type other than `type`.
|
||||
|
||||
### `type` and `Core.Type`
|
||||
|
||||
This proposal introduces the name `type` as a keyword -- a type literal -- that
|
||||
names the facet type with an empty set of constraints. This keyword replaces the
|
||||
provisional name `Type` for this functionality.
|
||||
|
||||
In [issue #2113](https://github.com/carbon-language/carbon-lang/issues/2113), it
|
||||
was decided that such functionality may be an alias for a name in the prelude,
|
||||
such as `Core.Type`. This proposal does not introduce a corresponding prelude
|
||||
name. The facet type `type` is primitive and provided by the compiler, and not
|
||||
an alias.
|
||||
|
||||
[As noted in the generics design](/docs/design/generics/details.md#named-constraints),
|
||||
a declaration such as `constraint MyVersionOfType {}` would introduce a facet
|
||||
type that is equivalent to `type`. This proposal does not change this decision.
|
||||
Because such a named constraint `MyVersionOfType` is equivalent to `type`, that
|
||||
is, because they are the same facet type, a value `T:! MyVersionOfType` is a
|
||||
type under this proposal. There is an open
|
||||
[issue for leads](https://github.com/carbon-language/carbon-lang/issues/2409) on
|
||||
whether this is the right behavior or if we'd prefer a different rule.
|
||||
|
||||
### Compound member access
|
||||
|
||||
#### Instance binding
|
||||
|
||||
For the syntax `x.y`, if `x` is an entity that has member names, such as a
|
||||
namespace or a type, then `y` is looked up within `x`, and instance binding is
|
||||
not performed. Otherwise, `y` is looked up within the type of `x` and instance
|
||||
binding is performed if an instance member is found.
|
||||
|
||||
The syntax `x.(Y)`, where `Y` names an instance member, always performs instance
|
||||
binding. Therefore, for a suitable `DebugPrintable`:
|
||||
|
||||
- `1.(DebugPrintable.Print)()` still prints `1`, as before.
|
||||
- `i32.(DebugPrintable.Print)()` now prints `i32`, rather than forming a
|
||||
member name.
|
||||
- `1.(i32.(DebugPrintable.Print))()` is now an error, rather than printing
|
||||
`1`.
|
||||
|
||||
In order to get the old effect of `x.(MyType.(MyInterface.InstanceMember))()`,
|
||||
one can now write `x.((MyType as MyInterface).InstanceMember)()`. This behaves
|
||||
the same as the member access in:
|
||||
|
||||
```
|
||||
fn F(MyType:! MyInterface, x: MyType) -> auto {
|
||||
return x.(MyType.InstanceMember)();
|
||||
}
|
||||
```
|
||||
|
||||
... because it is the same thing. `MyType as InstanceMember` is a facet, which
|
||||
roughly corresponds to a fully-instanstiated `impl`, and lookup into a facet
|
||||
finds members of that corresponding `impl`. And if `MyType` is declared as
|
||||
`MyType:! MyInterface` instead of as `MyType:! type`, then `MyType` and
|
||||
`MyType as MyInterface` are equivalent.
|
||||
|
||||
#### Impl lookup
|
||||
|
||||
Prior to this proposal, when a member access names a member of interface `I`,
|
||||
the rules for `impl` lookup also depend on whether the left hand operand is a
|
||||
type:
|
||||
|
||||
- If the left hand operand is a type-of-type (now, facet type), `impl` lookup
|
||||
is not performed.
|
||||
- If the left hand operand evaluates to a type `T`, then `impl` lookup is
|
||||
performed for `T as I`.
|
||||
- Otherwise, the first operand is an expression of some type `T`, and `impl`
|
||||
lookup is performed for `T as I`.
|
||||
|
||||
Due to the non-uniform treatment of types and non-types, this rule is also
|
||||
changed to the following:
|
||||
|
||||
- For a simple member access `a.b` where `b` names a member of an interface
|
||||
`I`:
|
||||
- If the interface member was found by searching a non-facet-type scope
|
||||
`T`, for example a class or an adapter, then `impl` lookup is performed
|
||||
for `T as I`. For example:
|
||||
- `MyClass.AliasForInterfaceMember` finds the member of the
|
||||
`impl MyClass as I`, not the interface member.
|
||||
- `my_value.AliasForInterfaceMember`, with `my_value: MyClass`, finds
|
||||
the member of the `impl MyClass as I`, and performs instance binding
|
||||
if the member is an instance member.
|
||||
- In the case where the member was found in a base class of the class
|
||||
that was searched, `T` is the derived class that was searched, not
|
||||
the base class in which the name was declared.
|
||||
- Otherwise, `impl` lookup is not performed:
|
||||
- `MyInterface.AliasForInterfaceMember` finds the member in the
|
||||
interface `MyInterface` and does not perform `impl` lookup.
|
||||
- For a compound member access `a.(b)` where `b` names a member of an
|
||||
interface `I`, `impl` lookup is performed for `T as I`, where:
|
||||
- If `b` is an instance member, `T` is the type of `a`.
|
||||
- `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value`
|
||||
is of type `MyClass`, uses `MyClass as Interface`.
|
||||
- `MyClass.(Interface.InstanceInterfaceMember)()` uses
|
||||
`type as Interface`.
|
||||
- Otherwise, `a` is implicitly converted to `I`, and `T` is the result of
|
||||
symbolically evaluating the converted expression.
|
||||
- `MyClass.(Interface.NonInstanceInterfaceMember)()` uses
|
||||
`MyClass as Interface`.
|
||||
- `my_value.(Interface.NonInstanceInterfaceMember)()` is an error
|
||||
unless `my_value` implicitly converts to a type.
|
||||
|
||||
This approach aims to change as little as possible of the existing `impl` lookup
|
||||
behavior while removing inconsistencies and behavioral differences based on
|
||||
whether a value is a type.
|
||||
|
||||
#### Examples
|
||||
|
||||
```
|
||||
interface Iface {
|
||||
fn Static();
|
||||
fn Method[me: Self]();
|
||||
}
|
||||
|
||||
impl type as Iface { ... }
|
||||
|
||||
fn F[T:! Iface](x: T) {
|
||||
// ✅ Uses `T as Iface`.
|
||||
x.Static();
|
||||
T.Static();
|
||||
// ❌ Uses `T as Iface`, but method call is missing an instance.
|
||||
T.Method();
|
||||
// ✅ OK, instance is provided. Uses `T as Iface`.
|
||||
x.Method();
|
||||
x.(T.Method)();
|
||||
|
||||
// ❌ Would use `(x as Iface).Static`.
|
||||
// Error because `x` is not a symbolic constant.
|
||||
x.(Iface.Static)();
|
||||
// ✅ Uses `(T as Iface).Static`.
|
||||
T.(Iface.Static)();
|
||||
// ✅ Uses `(type as Iface).Static`.
|
||||
type.(Iface.Static)();
|
||||
|
||||
// ✅ Uses `(T as Iface).Method`, with `me = x`.
|
||||
x.(Iface.Method)();
|
||||
// ✅ Uses `(type as Iface).Method`, with `me = T`.
|
||||
T.(Iface.Method)();
|
||||
}
|
||||
|
||||
base class Base {
|
||||
fn Static();
|
||||
fn Method[me: Self]();
|
||||
alias IfaceStatic = Iface.Static;
|
||||
alias IfaceMethod = Iface.Method;
|
||||
}
|
||||
external impl Base as Iface { ... }
|
||||
|
||||
fn G(b: Base) {
|
||||
// ✅ OK
|
||||
b.Static();
|
||||
Base.Static();
|
||||
// ❌ Uses `Base as Iface`, but method call is missing an instance.
|
||||
Base.Method();
|
||||
// ✅ OK
|
||||
b.Method();
|
||||
b.(Base.Method)();
|
||||
|
||||
// ✅ OK, same as `(Base as Iface).Static()`.
|
||||
b.IfaceStatic();
|
||||
Base.IfaceStatic();
|
||||
// ❌ Uses `Base as Iface`, but method call is missing an instance.
|
||||
Base.IfaceMethod();
|
||||
// ✅ OK, uses `Base as Iface`.
|
||||
b.IfaceMethod();
|
||||
b.(Base.IfaceMethod)();
|
||||
b.((Base as Iface).Method)();
|
||||
b.(Iface.Method)();
|
||||
}
|
||||
|
||||
class Derived extends Base {}
|
||||
external impl Derived as Iface { ... }
|
||||
|
||||
fn H(d: Derived) {
|
||||
// ✅ OK, calls `Base.Static`.
|
||||
d.Static();
|
||||
Derived.Static();
|
||||
// ❌ Uses `Derived as Iface`, but method call is missing an instance.
|
||||
Derived.Method();
|
||||
// ✅ OK, calls `Base.Method`.
|
||||
d.Method();
|
||||
d.(Base.Method)();
|
||||
d.(Derived.Method)();
|
||||
|
||||
// ✅ OK, same as `(Derived as Iface).Static()`.
|
||||
d.IfaceStatic();
|
||||
Derived.IfaceStatic();
|
||||
// ❌ Uses `Derived as Iface`, but method call is missing an instance.
|
||||
Derived.IfaceMethod();
|
||||
// ✅ OK, uses `Derived as Iface`.
|
||||
d.IfaceMethod();
|
||||
d.(Derived.IfaceMethod)();
|
||||
d.((Derived as Iface).Method)();
|
||||
d.(Iface.Method)();
|
||||
// ✅ OK, uses `Base as Iface`.
|
||||
d.(Base.IfaceMethod)();
|
||||
d.((Base as Iface).Method)();
|
||||
}
|
||||
```
|
||||
|
||||
### Tuple and struct literals
|
||||
|
||||
A tuple literal such as `(1, 2, 3)` produces a tuple value. Its type cannot be
|
||||
written directly as a literal: the literal `(i32, i32, i32)` also produces a
|
||||
tuple value, not a type value. However, the type of `(1, 2, 3)` is easy to
|
||||
express: it is the result of implicitly converting `(i32, i32, i32)` to a type,
|
||||
so for example `(i32, i32, i32) as type` evaluates to the type of `(1, 2, 3)`.
|
||||
|
||||
There is no conversion from a tuple type to a tuple value, because tuple types
|
||||
are values of type `type`, and the type `type` does not implicitly convert to a
|
||||
tuple type. For metaprogramming, it is likely that we will benefit from having a
|
||||
mechanism to convert a tuple type into a tuple of types, but this is likely to
|
||||
be possible using a variadic:
|
||||
|
||||
```
|
||||
// Takes a tuple of values. Returns a tuple containing the types of those values.
|
||||
fn TupleTypeToTupleOfTypes[Types:! type,...](x: (Types,...)) -> auto {
|
||||
return (Types,...);
|
||||
}
|
||||
```
|
||||
|
||||
### Name lookup into values with constrained types
|
||||
|
||||
Given a generic function with a generic type parameter, uses of that parameter
|
||||
will be implicitly converted to type `type`. For example:
|
||||
|
||||
```
|
||||
fn F[T:! Printable](x: T) { x.Print(); }
|
||||
```
|
||||
|
||||
... is equivalent to ...
|
||||
|
||||
```
|
||||
fn F[T:! Printable](x: T as type) { x.Print(); }
|
||||
```
|
||||
|
||||
As a result, we can no longer describe lookup for `x` as looking into the
|
||||
type-of-type, that is, the type of the expression after `x:`, because after
|
||||
implicit conversion, that expression is of type `type`. Instead, lookup will
|
||||
look into the type of `x`, which symbolically evaluates to the value
|
||||
corresponding to `T` in type `type`, and will look at that value to determine
|
||||
where else to look. Because that value is symbolically the name of a generic
|
||||
type parameter, we will look in the type of `T`, which is `Printable`.
|
||||
|
||||
### Types in diagnostics
|
||||
|
||||
Concern has been raised that this proposal could lead to `as type` appearing
|
||||
frequently in diagnostics when types are printed. This is not expected to be the
|
||||
case. When a type appears in a diagnostic, it will generally be in one of two
|
||||
forms:
|
||||
|
||||
- A reflection of source-level syntax that the programmer wrote.
|
||||
- A string generated by the compiler to describe the type to the programmer.
|
||||
|
||||
In both cases, we expect the type of a variable such as `var v: (i32, i32)` to
|
||||
be displayed as `(i32, i32)`, not as `(i32, i32) as type`, unless either the
|
||||
context requires the `as type` suffix for disambiguation or the programmer wrote
|
||||
it in the source code.
|
||||
|
||||
This is analogous to how integer literals behave: when describing the value `1`
|
||||
of type `i32` in a diagnostic, the type is usually treated as assumed context,
|
||||
so the diagnostic would say simply `1` rather than `1 as i32`. Similarly, C++
|
||||
compilers typically avoid printing things like `(short)5` when including values
|
||||
of type `short`, for which there is no literal syntax, in diagnostics.
|
||||
|
||||
## Rationale
|
||||
|
||||
- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
|
||||
- Making the behavior of tuples and empty structs more like other types is
|
||||
easier for tooling to handle.
|
||||
- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
|
||||
- The transition between templates and generics is made smoother by
|
||||
changing compound member access to behave consistently regardless of
|
||||
whether the left-hand operand is a type.
|
||||
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
||||
- Bottoming out at the same type, `type`, for all types is likely to be
|
||||
easier to understand. However, having `(i32, i32)` not be the type of a
|
||||
pair of integers, but instead be _implicitly convertible to_ the type of
|
||||
a pair of integers, is likely to be harder to understand. It's unclear
|
||||
which of these will be more dominant, but the new behavior is more
|
||||
consistent.
|
||||
|
||||
### Teachability
|
||||
|
||||
One of the bigger concerns with this proposal is how teachable it is. The
|
||||
details of this model are likely to be challenging for new-comers to Carbon,
|
||||
even ones with experience in C++.
|
||||
|
||||
When explaining this model, we suggest focusing on the goal and how it is
|
||||
achieved, rather than what happens under the covers. For example, we can say
|
||||
|
||||
> You can use `var n: (i32, i32)` to declare that `n` is a pair of `i32`s
|
||||
|
||||
without mentioning that the actual type of `(i32, i32)` is `(type, type)`, and
|
||||
that an implicit conversion is performed here. This is analogous to how we can
|
||||
say
|
||||
|
||||
> You can use `n = (1, 2);` to assign `1` to the first element of `n` and `2` to
|
||||
> the second element
|
||||
|
||||
without mentioning that again an implicit conversion is performed.
|
||||
|
||||
In practice, this is a relatively small change from Carbon's design prior to
|
||||
this proposal, and there are also teachability concerns with the ability to
|
||||
treat a tuple of types as a type despite it _not_ being a value of type `type`.
|
||||
Should there prove to be sustained problems with teachability, we should
|
||||
consider making more significant changes, such as adopting a distinct syntax for
|
||||
tuple types and empty structs.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Alternative member access rules
|
||||
|
||||
We considered rules where `T.(Interface.Member)` would either always mean
|
||||
`(T as Interface).Member` or always mean `(typeof(T) as Interface).Member`, but
|
||||
that did not allow aliases to work as expected. We wanted an alias to an
|
||||
interface method or class function defined inside of a class to be usable as a
|
||||
method or class function member of the class.
|
||||
|
||||
```
|
||||
interface A {
|
||||
fn F[me: Self]();
|
||||
fn S();
|
||||
}
|
||||
class B {
|
||||
external impl as A;
|
||||
alias G = A.F;
|
||||
fn H[me: Self]();
|
||||
alias T = A.S;
|
||||
fn U();
|
||||
}
|
||||
```
|
||||
|
||||
We want `B.G` to act like a method of `B`, like `B.H`, and `B.T` to act like a
|
||||
static class function of `B`, like `B.U`. We could have accomplished this by
|
||||
requiring those aliases to be written in a different way, for example
|
||||
`alias G = (Self as A).F`, but needing to do that was surprising. We made it
|
||||
work by making the interpretation of `T.(Interface.Member)` depend on whether
|
||||
`Member` was an _instance_ member of `Interface`, with an implicit `me`
|
||||
parameter, or not.
|
||||
|
||||
This was discussed in
|
||||
[the #syntax channel on discord on 2022-11-07](https://discord.com/channels/655572317891461132/709488742942900284/1039298322625744987)
|
||||
and in
|
||||
[open discussion on 2022-11-10](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.1y65biik2vr).
|
||||
|
||||
### Alternative terminology
|
||||
|
||||
We considered various alternatives for the terminology choices listed above in
|
||||
[open discussion on 2022-10-27](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.lecxe8qywdxa).
|
||||
|
||||
Instead of "facet type" we considered:
|
||||
|
||||
- constraint type
|
||||
- Concern: a "foo type" can mean "a type that is also a foo" like "class
|
||||
type" or it can mean "a type that holds foos" like "integer type".
|
||||
- constrained type
|
||||
- Concern: for some of us, this name seemed to better describe facets than
|
||||
facet types.
|
||||
- constraint
|
||||
- Concern: this may sound like it should refer to a boolean predicate,
|
||||
`T is C`, not `C` itself.
|
||||
- kind
|
||||
- Concern: while this terminology is used to describe the type of a type
|
||||
in computer science literature, that usage is different from this one in
|
||||
that it generally refers to the arity of type constructors rather than
|
||||
classifying subsets of the set of types.
|
||||
|
||||
Instead of "generic type" we considered:
|
||||
|
||||
- archetype
|
||||
- Concern: the usage here didn't match some of our intuitions based on
|
||||
existing usage of this term.
|
||||
- Concern: while this might fit usage within the scope of the parameter,
|
||||
it fits usage from outside the scope less well. It seemed awkward to say
|
||||
that a class has an archetype parameter, as opposed to our chosen
|
||||
terminology of a generic type parameter.
|
||||
- constrained type variable
|
||||
- Concern: for some of us, the name "constrained type" seemed to better
|
||||
describe facet types than facets.
|
||||
- type-like
|
||||
- Concern: derived terms like "generic type-like parameter" and
|
||||
"associated type-like constant" are awkward.
|
||||
- kinded
|
||||
- Concern: derived terms like "generic kinded parameter" and "associated
|
||||
kinded constant" are awkward.
|
||||
|
||||
Instead of "facet" we considered:
|
||||
|
||||
- type
|
||||
- The idea here is that, because facets can be used in type contexts,
|
||||
calling them types is reasonable.
|
||||
- Concern: we want `i32 as Sortable` and `i32 as Hashable` to be
|
||||
different, but we do not want to say that they are "different types".
|
||||
- Concern: we use `type` to refer to types, not facets, and it's important
|
||||
that conversion to type `type` erases the facet type from a facet. If we
|
||||
call facets "types" then we mean different things by "type" and
|
||||
"`type`", and for example conversion of a type to type `type` would
|
||||
change its meaning, which seems surprising.
|
||||
- impl
|
||||
- Concern: facets are analogous to `impl`s but are not in direct
|
||||
correspondence, because a facet can be for a constraint that involves
|
||||
multiple interfaces and hence potentially multiple `impl`s, and an
|
||||
`impl` can be for a constraint that involves multiple interfaces and
|
||||
hence a facet can refer to only _part of_ an `impl`.
|
||||
- archetype
|
||||
- Concern: this seems inappropriate for non-generic facets, such as
|
||||
`i32 as Hashable`.
|
||||
- witness
|
||||
- Concern: doesn't quite represent what we mean: a facet is more like a
|
||||
pair of a type and a witness that that type implements the facet's type.
|
||||
|
||||
### `Core.Type` is an empty named constraint
|
||||
|
||||
We considered making `type` an alias for an empty type constraint defined in the
|
||||
prelude, `Core.Type`. While it is possible to define a constraint that is
|
||||
equivalent to `type` as a named constraint, we decided not to define `type` as
|
||||
an alias to such a named constraint in order to reduce complexity:
|
||||
|
||||
- Type-checking the prelude itself becomes more challenging if `type` is a
|
||||
named constraint, due to the ubiquity of its usage.
|
||||
- If `type` is an alias to `Core.Type`, then either the implementation would
|
||||
need to ensure that it introduces no member names and no constraints, or it
|
||||
would need to faithfully look into `Core.Type` every type a property of
|
||||
`type` is consulted, adding either compile-time cost or complexity to the
|
||||
implementation.
|
||||
|
||||
Given that we have no current desire to add member names or constraints to
|
||||
`type`, we do not get any benefit from defining it in the Carbon library. If we
|
||||
later choose to make `type` imply some other constraints by default, such as
|
||||
`Sized` or `Movable`, this decision should be revisited.
|
||||
|
||||
This was discussed in:
|
||||
|
||||
- [#naming on 2022-10-31](https://discord.com/channels/655572317891461132/963846118964350976/1036750048899366954)
|
||||
- [#syntax on 2022-10-27](https://discord.com/channels/655572317891461132/709488742942900284/1035222720864071740)
|
||||
- [open discussion on 2022-10-31](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.9x8m0qwhuux)
|
||||
|
||||
### Write tuple types differently than tuples
|
||||
|
||||
We considered having the way to write tuple types be different than tuples. So
|
||||
instead of `var x: (i32, i32) = (1, 2);` you might write:
|
||||
|
||||
```carbon
|
||||
var x: TupleType(i32, i32) = (1, 2);
|
||||
|
||||
// Or, with dedicated syntax:
|
||||
var y: (|i32, i32|) = (1, 2);
|
||||
```
|
||||
|
||||
There are a few reasons we decided not to go with this approach:
|
||||
|
||||
- There is a significant cost to having more balanced delimiters, particularly
|
||||
ones consisting of multiple characters.
|
||||
- Since types are values in Carbon, `(i32, i32)` is a valid and meaningful
|
||||
expression. Users would be surprised if they cannot use it as a type.
|
||||
- Allowing tuples in pattern syntax but not in type syntax may be surprising.
|
||||
For example, given that `var (x: i32, y: i32);` is valid, it may be
|
||||
surprising if `var p: (i32, i32);` is not valid.
|
||||
- We expect that, when teaching or speaking casually, we would not need to
|
||||
explain that `(i32, i32)` was not a type but was usable as a type. That
|
||||
distinction would generally not affect users, who would generally be able to
|
||||
treat the type of a tuple as the tuple of the types of its elements.
|
||||
- This would prevent operations from working generically on both tuples values
|
||||
and tuple types. We would need to instead duplicate the set of operations.
|
||||
For example, there would need to be separate `TupleCat` and `TupleTypeCat`
|
||||
operations to perform concatenation. This would be extra machinery, and a
|
||||
burden for users to know about all of the functions and call the right one.
|
||||
|
||||
This was discussed
|
||||
[in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166)
|
||||
and
|
||||
[in open discussion on on 2022-12-07](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.qu6e01wbzrhz).
|
||||
Reference in New Issue
Block a user