Should we move away from `:!` syntax for generics and templates?
leads question
Historically, the idea for `:!` was anchored on the idea of "phase" and compile time phases being more "now", and so having an exclamation!
But this isn't holding up all that well with time. Some specific concerns I have or have heard:
- Doesn't work well for controlling the phase for functions, we've started using `eval` and `musteval` here which seem like a much better fit.
- The connection between generics and templates to `!` is too tenuous to serve as an effective mnemonic
- It is _very_ inventive syntax with little familiarity from any other languages
- It makes Carbon code using generics look somewhat ASCII-art -- too much dense punctuation.
- There are likely more compelling use cases for `!` such as for operations that are required to succeed or terminate, especially unwrapping of optionals. These would be difficult to explain and at least visually ambiguous if `!` was used for both purposes.
In the very earliest days of Carbon's syntax, we explored a syntax alternative that I think we should revisit. I believe Josh originally proposed this syntax, but it was a long time ago and I'm not sure it ended up captured in docs, so apologies if I've made a mistaken or forgotten some aspect.
The key idea is to use _contextual defaults_ for phase, because for each context we have a very strong default option. And when the default doesn't apply, have keywords to select other options.
Candidate keywords:
- `template` just as we already use for template parameters today in conjunction with `:!`
- `generic` for symbolic generic parameters (just a `:!` today)
- Some alternatives that I don't like quite as much at least personally:
- `symbolic` feels in some ways more technically accurate but it seems much less accessible and teachable
- `comptime` reflects a fact about the parameter, but not really _why_ or _how it is used_
- Overall, I find the mnemonic devices available for `generic` much stronger than for the other keywords I've thought of, but more ideas welcome.
- `runtime` for runtime parameters (I'm least confident in this one)
- Alternatives:
- `dynamic` uses the term dynamic in a reasonable way, but i think risks confusion with dynamic dispatch use cases such as Rust's `dyn`
- Less confident about this one FWIW.
Then the contextual defaults would be:
- Parameters to compile time entities like interfaces, impls, and classes are `generic` by default and can be marked as `template`.
- Deduced function parameters also default to `generic` by default, but can be either marked `template` or potentially `runtime` if we end up wanting to put something like implicit scoped parameters there
- Note that this default somewhat builds on #6931 moving `self` to the explicit parameters
- Explicit function parameters default to `runtime`, but can be marked as either `generic` or `template`.
The expectation is that these defaults work well enough to make keywords reasonably sparse and avoid verbosity overload, but still have clear and explicit syntax.
Some example code using this syntax:
```carbon
// Default `generic` on `T`.
interface I(T: type) {
fn Op(self, arg: T) -> T;
}
// Default `generic` on `T`.
class C(T: type, template U: type) {
// Default `generic` on `V1`
fn Method1[V1: type](self, arg: V1) -> V1;
var field1: T;
var field2: U;
}
fn F1[Q: type](arg1: Q, generic QQ: type, arg2: QQ) -> (Q, QQ);
fn F2[Q: type](arg1: Q, template QQ: type, arg2: QQ) -> (Q, QQ);
// If we want scoped runtime parameters in `[]`s:
fn ScopedRuntimeParams[runtime heap: Heap](generic T: type) -> T*;
// A C++-`constexpr`-style function
eval fn ConstexprF[T: type](arg: T) -> T;
// A C++-`consteval`-style function
musteval ConstevalF[T: type](template U: type, arg: (U, T)) -> (T, U)
```
I had an earlier doc where I was brainstorming alternatives if folks want to look through, but it doesn't have as much context as the above: https://docs.google.com/document/d/1faYBY4Rt-3_8iL7YUk6HGsQAiX4CK8xizxFvg8K64LM/edit?tab=t.0
19 条评论