From 5cf333748e1d3eee76dfbaca56ddae7b64610ffd Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Wed, 20 Sep 2023 21:18:20 +0200 Subject: [PATCH] add "use = not : for initialization and named parameters" draft --- ...for-initialization-and-named-parameters.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 _languages/use-equals-not-colon-for-initialization-and-named-parameters.md diff --git a/_languages/use-equals-not-colon-for-initialization-and-named-parameters.md b/_languages/use-equals-not-colon-for-initialization-and-named-parameters.md new file mode 100644 index 0000000..7c32a33 --- /dev/null +++ b/_languages/use-equals-not-colon-for-initialization-and-named-parameters.md @@ -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?