-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add "use = not : for initialization and named parameters" draft
- Loading branch information
Showing
1 changed file
with
99 additions
and
0 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
_languages/use-equals-not-colon-for-initialization-and-named-parameters.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
title: "Language Design: Use `=`, not `:` for Initialization and Named Parameters (WIP)" | ||
date: 2023-09-20 | ||
--- | ||
|
||
# DRAFT | ||
|
||
## The Problem, explained using Rust as a cautionary tale | ||
|
||
Rust has two distinct syntactic variants for "invocations"[^1] ... | ||
- one for calling functions and | ||
- one for initializing structs and enums[^2] | ||
|
||
... that provide different affordances and features: | ||
|
||
- struct initializer arguments can be named, and can also use a shorthand notation | ||
- method arguments cannot be named, and what looks like shorthand notation is just positional arguments | ||
|
||
The following example shows both: | ||
|
||
```rust | ||
struct User { | ||
active: bool, | ||
username: String, | ||
email: String, | ||
sign_in_count: u64, | ||
} | ||
|
||
fn user(email: String, username: String) -> User { | ||
User { | ||
active: true, | ||
username, | ||
email, | ||
sign_in_count: 0, | ||
} | ||
} | ||
|
||
fn main() { | ||
user("jane@example.com".into(), "Jane Example".into()); | ||
} | ||
``` | ||
|
||
### Diverging Code Styles and Best Practices | ||
|
||
In practice this means that you have diverging recommendations and best practices (option structs, default hacks, builders etc.) | ||
for dealing with issues like "this thing has grown and is taking way too many parameters now". | ||
|
||
And every improvement that is going to be made will change the scale slightly, causing churn due to another thing becoming the next "best practice". | ||
(And most likely only apply to either struct/enum init *or* method calls, but not both.) | ||
|
||
|
||
### Needless Ambiguity | ||
|
||
Structs using `{` also means that something as trivial as `if foo {` is ambiguous in Rust. | ||
|
||
|
||
### Lack of Consistent Rules for Type Ascriptions | ||
|
||
It breaks the consistency of always being able to rely on while reading that `: Foo` is a type ascription. | ||
Languages from the 70ies managed to get this right, Rust somehow regressed on that, failing to ship | ||
[type ascriptions](https://rust-lang.github.io/rfcs/0803-type-ascription.html) and | ||
[giving up on it after 8 years](https://rust-lang.github.io/rfcs/3307-de-rfc-type-ascription.html). | ||
|
||
|
||
### Default Parameters Made Hard | ||
|
||
Using `:` for struct initialization means that it's not a good syntax options for extending the language for default parameters: | ||
|
||
struct X { x: String: "foo" } // ugly | ||
|
||
## The Solution | ||
|
||
What should they have done: | ||
|
||
- Function calls, method calls, struct init, enum init consistently use `()`, not a random mix of `{}` and `()` | ||
- Have *one* ruleset that all those invocation follow. | ||
- Use `=` for named parameters, such that everything works if default parameters are added in the future. | ||
- Shorthand notation or positional arguments? Pick one. | ||
|
||
#### How to Distinguish between a variable assigment and a named parameter use inside a function invocation? | ||
|
||
Example: | ||
|
||
fun someFunction(a: Int64) = ... | ||
var a = 12; | ||
let b = 23 | ||
someFunction(a = b) // what does this mean? | ||
|
||
Single rule: inside a functions argument list, the first level of `=` use is always a named parameter and never a | ||
variable assignment, even if some variable `a` would be in scope.[^3] | ||
|
||
If a variable assigment inside a function argument list is still needed, it could be expressed as: | ||
|
||
someFunction(a = { a = b; b }) // still possible | ||
|
||
|
||
[^1]: Using the term "invocations" loosely here. It's almost guaranteed that Rust fans show up anyway going "ackchyually" while completely missing the point, but hey – I tried. | ||
[^2]: Except the tupled version of enums, for which initializers look like function calls again. | ||
[^3]: Also, variable assignment returns `Unit`, so we never have to decide, right? RIGHT? |