effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
332 stars 24 forks source link

Record update syntax? #557

Open marzipankaiser opened 2 months ago

marzipankaiser commented 2 months ago

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

b-studios commented 2 months ago

Many languages use keyword + default arguments, as you mention. Why do you think those are difficult to implement?

jiribenes commented 2 months ago

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.

marzipankaiser commented 2 months ago

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).