Record update syntax?
Summary
It would be nice to have a syntax for record update in Effekt (the copying, immutable kind).
Proposal
Generate "update*" functions, so we can have:
record Foo(a: Int, b: Int)
val default = Foo(12,9)
val x = default.updateA(10)
println(x) // Foo(10,9)
Feel free to suggest better names/syntax...
Motivating use case
Some libraries may have multiple options, e.g. a regex library may have options for case sensitivity, multiline mode, ...
A default options record could then be passed or first updated using record update syntax (as common in e.g. Haskell).
Alternatives for this use case
- keyword + default arguments (probably harder to implement?), also potentially needs: forwarding.
- Instead of a record, effects could be used, but unless we have one single-operation effect per option, this does not solve the "set a single one" problem (but the equivalent "forward remaining operations" may be interesting).
Many languages use keyword + default arguments, as you mention. Why do you think those are difficult to implement?
There's also the TypeScript/Rust option: spreads which look like val x = Foo(a = 10, ..default), so the syntax for instantiating a record becomes
<record_inst> ::= <record_ident> `(` (<ident> `=` <expr> `,`? )* (`..` <expr>)? `)`
| <record_ident> `(` (<expr> `,`? )* `)`
^ Allows just one spread in the tail position when using keyword arguments for the fields of the struct. The spread is used as a default for all otherwise uninitialised fields. The second variant allows only unnamed arguments and no spreads. Note that the first variant also encompasses a copying operation: val copy = Foo(..default).
So in this case, the example would look like:
record Foo(a: Int, b: Int)
val default = Foo(12, 9) // or: Foo(a = 12, b = 9)
val x = Foo(a = 10, ..default)
println(x) // Foo(10, 9)
I'm not sure I prefer this variant, but I thought to mention it since it looks a bit different than the other variants.
Many languages use keyword + default arguments, as you mention. Why do you think those are difficult to implement?
"Difficult" as in a bigger change to the codebase (they are of course simple to add in principle).
IIUC they would need to be changed in the parser and added in the source.Tree (+ handled in Namer, Typer) and translation to core (at least).
For the defaults there are multiple options on what is allowed and when this will be evaluated (from the top of my head: insert at call site, desugar to overloaded variant of the function, global constant).