v1.0.1-SNAPSHOT: named args in calls

This commit is contained in:
sergeych committed 2025-11-27 11:24:50 +01:00
1 parent d6e6d68b18
commit 2d721101dd
14 files changed
+695 -170

No files matched your search

+48 -1
View File
@@ -5,7 +5,7 @@ lambdas and class declarations.
## Regular
## default values
## Default values
Default parameters should not be mixed with mandatory ones:
@@ -95,6 +95,53 @@ There could be any number of splats at any positions. You can splat any other [I
testSplat("start", ...range, "end")
>>> [start,1,2,3,end]
>>> void
## Named arguments in calls
Lyng supports named arguments at call sites using colon syntax `name: value`:
```lyng
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
assertEquals(["foo", "b", "bazz"], test(b: "b"))
assertEquals(["a", "bar", "c"], test("a", c: "c"))
```
Rules:
- Named arguments must follow positional arguments. After the first named argument, no positional arguments may appear inside the parentheses.
- The only exception is the syntactic trailing block after the call: `f(args) { ... }`. This block is outside the parentheses and is handled specially (see below).
- A named argument cannot reassign a parameter already set positionally.
- If the last parameter has already been assigned by a named argument (or named splat), a trailing block is not allowed and results in an error.
Why `:` and not `=` at call sites? In Lyng, `=` is an expression (assignment), so we use `:` to avoid ambiguity. Declarations continue to use `:` for types, while call sites use `as` / `as?` for type operations.
## Named splats (map splats)
Splat (`...`) of a Map provides named arguments to the call. Only string keys are allowed:
```lyng
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
val r = test("A?", ...Map("d" => "D!", "b" => "B!"))
assertEquals(["A?","B!","c","D!"], r)
```
Constraints:
- Map splat keys must be strings; otherwise, a clean error is thrown.
- Named splats cannot duplicate parameters already assigned (by positional or named arguments).
- Named splats must follow all positional arguments and positional splats.
- Ellipsis parameters (variadic) remain positional-only and cannot be assigned by name.
## Trailing-lambda rule interaction
If a call is immediately followed by a block `{ ... }`, it is treated as an extra last argument and bound to the last parameter. However, if the last parameter is already assigned by a named argument or a named splat, using a trailing block is an error:
```lyng
fun f(x, onDone) { onDone(x) }
f(x: 1) { 42 } // ERROR
f(1) { 42 } // OK
```
[tutorial]: tutorial.md
+80
View File
@@ -0,0 +1,80 @@
Note for AI. please ignore this file until this remark is removed.
# Map literals proposal
Note to the AI: please ignore this file until this note is removed.
Implement javascript-like literals for maps. It uses the similar syntax already adopted for named arguments in function calls so it should be organic and familiar, as the purpose is similar.
String literal keys
```lyng
val m = { a: "foo", b: "bar" }
assertEqual(m.a, "foo")
assertEqual(m.b, "bar")
```
The map literal is a map with keys that are string literals only. This is important. In a relatively rare case when keys are calculated, or extravagant but still possible case when keys are of different types, literal could be combined with "=>":
```lyng
val k1 = "bar"
val m = { "foo": 123 } + k1 => "buzz"
// this is same as Map("foo" => 123) + Map("bar" => k2) but can be optimized by compiler
assertEqual(m["foo"], 123)
assertEqual(m["bar"], "buzz")
```
The lambda syntax is different, it can start with the `map_lteral_start` above, it should produce compile time error, so we can add map literals of this sort.
Also, we will allow splats in map literals:
```
val m = { foo: "bar", ...{bar: "buzz"} }
assertEquals("bar",m["foo"])
assertEquals("buzz", m["bar"])
```
When the literal argument and splats are used together, they must be evaluated left-to-right with allowed overwriting
between named elements and splats, allowing any combination and multiple splats:
```
val m = { foo: "bar", ...{bar: "buzz"}, ...{foo: "foobar"}, bar: "bar" }
assertEquals("foobar",m["foo"])
assertEquals("bar", m["bar"])
```
Still we disallow duplicating _string literals_:
```
// this is an compile-time exception:
{ foo: 1, bar: 2, foo: 3 }
```
Special syntax allows to insert key-value pair from the variable which name should be the key, and content is value:
```
val foo = "bar"
val bar = "buzz"
assertEquals( {foo: "bar", bar: "buzz"}, { foo, bar } )
```
So, summarizing, overwriting/duplication rules are:
- string literals can't duplicate
- splats add or update content, effectively overwrite preceding content,
- string literals overwrite content received from preceding splats (as no duplication string literal keys allowed)
- the priority and order is left-to-right, rightmost wins.
- var inclusion is treated as form of the literal
This approach resolves the ambiguity from lambda syntax, as
```ebnf
ws = zero or more whitespace characters including newline
map_literal start = "{", ws, (s1 | s2 | s3)
s1 = string_literal, ws, ":", ws, expression
s2 = "...", string_literal
s3 = string_literal, ("," | "}")
```
is not a valid lambda beginning.
+67
View File
@@ -0,0 +1,67 @@
# Named arguments proposal
Extend function/method calls to allow setting arguments by name using colon syntax at call sites. This is especially convenient with many parameters and default values.
Examples:
```lyng
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
assertEquals(test(b: "b"), ["foo", "b", "bazz"])
assertEquals(test("a", c: "c"), ["a", "bar", "c"])
```
Rules:
- Named arguments are optional. If named arguments are present, their order is not important.
- Named arguments must follow positional arguments; positional arguments cannot follow named ones (the only exception is the syntactic trailing block outside parentheses, see below).
- A named argument cannot reassign a parameter already set positionally.
- If the last parameter is already assigned by a named argument (or named splat), the trailing-lambda rule must NOT apply: a following `{ ... }` after the call is an error.
Rationale for using `:` instead of `=` in calls: in Lyng, assignment `=` is an expression; using `:` avoids ambiguity and keeps declarations (`name: Type`) distinct from call sites, where casting uses `as` / `as?`.
Migration note: earlier drafts/examples used `name = value`. The final syntax is `name: value` at call sites.
## Extended call argument splats: named splats
With named arguments, splats (`...`) are extended to support maps as named splats. When a splat evaluates to a Map, its entries provide name→value assignments:
```lyng
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
assertEquals(test("A?", ...Map("d" => "D!", "b" => "B!")), ["A?", "B!", "c", "D!"])
```
Constraints for named splats:
- Only string keys are allowed in map splats; otherwise, a clean error is thrown.
- Named splats cannot reassign parameters already set (positionally or by earlier named arguments/splats).
- Named splats follow the same ordering as named arguments: they must appear after all positional arguments and positional splats.
## Trailing-lambda interaction
Lyng supports a syntactic trailing block after a call: `f(args) { ... }`. With named args/splats, if the last parameter is already assigned by name, the trailing block must not apply and the call is an error:
```lyng
fun f(x, onDone) { onDone(x) }
f(x: 1) { 42 } // ERROR: last parameter already assigned by name
f(1) { 42 } // OK
```
## Errors (non-exhaustive)
- Positional argument after any named argument inside parentheses: error.
- Positional splat after any named argument: error.
- Duplicate named assignment (directly or via map splats): error.
- Unknown parameter name in a named argument/splat: error.
- Map splat with non-string keys: error.
- Attempt to target the ellipsis parameter by name: error.
## Notes
- Declarations continue to use `:` for types, while call sites use `:` for named arguments and `as` / `as?` for type casts/checks.