XGo already supports a family of "command-style" calls that take a trailing lambda expression, written with an explicit =>:
times 3, => {
... // repeat this block 3 times
}
forEver => {
... // repeat this block forever
}
onKey KeySpace, => {
... // triggered when the space key is pressed
}
This works uniformly across all XGo code today. However, in certain classfiles — most notably game/animation-oriented ones such as spx — this pattern is extremely common, and the => { boilerplate adds visual noise to what is conceptually closer to a native control-flow construct (a loop, an event handler, etc.).
We want classfile authors to be able to opt in to a terser form for specific commands, so the same code can instead be written as:
times 3 {
... // repeat this block 3 times
}
forEver {
... // repeat this block forever
}
onKey KeySpace {
... // triggered when the space key is pressed
}
We call this feature Auto Lambda: the trailing { ... } block is automatically recognized as a lambda argument, without requiring =>.
Auto Lambda is strictly a parser-level sugar. It introduces no new runtime semantics and no new AST node kinds — it only changes how a specific, explicitly opted-in set of command calls may be spelled.
Non-Goals
- Auto Lambda is not a general replacement for
=>. Plain function calls, and command calls in classfiles that have not enabled Auto Lambda for that command, must continue to use the existing cmd args..., => { ... } form.
- Auto Lambda does not change how lambdas are compiled, how closures capture variables, or how command calls are type-checked. It only affects surface syntax recognition during parsing.
- Auto Lambda is scoped per classfile and per command name. It is never a global language change; it cannot be enabled for ordinary (non-classfile) XGo source files.
Activating Auto Lambda
A classfile enables Auto Lambda for a specific set of commands by adding an autolambda directive to its gox.mod:
autolambda times(1), forEver(0), onKey(1)
Each entry has the form name(n), where:
name is the command's identifier (the function/method name as called in XGo source, e.g. times, forEver, onKey).
n is the number of non-lambda arguments the command takes, i.e. the arguments that appear before the (now implicit) trailing lambda.
For example:
| Directive entry |
Full call form |
Meaning |
times(1) |
times 3, => { ... } |
1 argument (3) before the lambda |
forEver(0) |
forEver => { ... } |
0 arguments before the lambda |
onKey(1) |
onKey KeySpace, => { ... } |
1 argument (KeySpace) before the lambda |
A single gox.mod may declare Auto Lambda for multiple commands in one directive, as shown above, or across multiple autolambda lines.
Parsing Rules
When the parser is processing source belonging to a classfile whose gox.mod declares autolambda for a command name(n), it applies the following rule while parsing a command-style call to name:
- Parse
name followed by exactly n comma-separated arguments, exactly as it would for a normal command call.
- After those
n arguments, instead of requiring , => {, the parser also accepts a bare { directly starting a block.
- If a bare
{ is found in that position, it is parsed as the lambda body and treated exactly as if the source had written , => { (or, when n == 0, => {) at that point.
- The explicit
=> form remains valid at all times, even for commands that have Auto Lambda enabled. Auto Lambda only adds an alternative spelling; it never removes the original one.
If a command with Auto Lambda enabled is called with a different number of leading arguments than declared (e.g. times called with 2 arguments before a bare {), the bare { is not treated as an implicit lambda; ordinary parsing rules apply, and this will surface as a normal type/argument-count error at the call site, not as an Auto Lambda-specific error. This keeps the feature's parsing rule simple: it only fires when the argument count exactly matches what gox.mod declares.
Why this doesn't need => to disambiguate
Outside of Auto Lambda, => exists so the parser can tell a command call with a trailing lambda apart from a command call statement followed by an unrelated block statement, or from other constructs that could start with {. Because Auto Lambda is enabled per command name, and only inside classfiles that explicitly opt in, the parser already knows — at the moment it starts parsing a call to times, forEver, or onKey in such a classfile — that a bare { immediately following the declared argument count can only mean one thing: the lambda body. There is no ordinary XGo statement form where a command call is immediately followed by a { block that means something else, so no ambiguity is introduced.
Under the Hood
Auto Lambda does not change code generation, type checking, or overload resolution. Once parsed, times 3 { ... } produces the same LambdaExpr node — with the same argument list and body — as times 3, => { ... } would have.
The one AST-level change is a new boolean field on LambdaExpr:
When the parser desugars a bare { ... } into a LambdaExpr because => was omitted (i.e. the Auto Lambda form was used), it sets AutoLambda to true. When the source uses the explicit => { ... } form, AutoLambda is left as its zero value, false. In both cases the rest of the node (the lambda's parameters and body) is populated identically.
Consequently:
- Type checking, overload resolution, and
gox code generation do not need to branch on AutoLambda at all — the field exists purely so that source position/spelling information isn't lost, not because the two forms compile differently.
- Tools that consume the XGo AST (formatters, linters, later compiler passes) can use
AutoLambda if they need to preserve or reproduce the original spelling (for example, a formatter that wants to print times 3 { ... } back out instead of expanding it to times 3, => { ... }), but no compiler stage is required to treat the two forms differently.
Example: spx-style Classfile
gox.mod:
autolambda times(1), forEver(0), onKey(1), onStart(0)
Usage:
onStart {
say "Hello, XGo!"
}
forEver {
step 1
}
times 5 {
turn 90
}
onKey KeySpace {
jump
}
Note the last call still uses the explicit => form — this remains valid even though onKey has Auto Lambda enabled, since Auto Lambda only adds an alternate, terser spelling rather than replacing the existing one.
Compatibility
- Files outside of any classfile, or classfiles whose
gox.mod does not declare autolambda for a given command, are entirely unaffected: such commands must continue to use => as they do today.
- Enabling
autolambda for a command in gox.mod is purely additive and backward compatible — all existing call sites using => keep compiling unchanged.
- Because the argument count
n is declared explicitly in gox.mod rather than inferred from a function signature, Auto Lambda works uniformly for overloaded commands, as long as every overload that is meant to support Auto Lambda shares the same leading-argument count n.
Open Questions
-
Should gox.mod allow declaring Auto Lambda for a command with more than one possible leading-argument count (e.g. supporting both times(1) and a future times(2) overload)? The current proposal assumes a single n per command name; extending this is left for future work if a concrete use case arises.
One possible future direction is a special value times(-1), meaning "support a variable number of leading arguments" rather than a fixed count. However, this cannot be made to work while also preserving backward compatibility with the explicit => { form: with a fixed n, the parser knows exactly where the argument list ends and can therefore accept either => or a bare { at that boundary; with a variadic argument count, the parser has no fixed boundary to look for, so a bare { becomes the only reliable signal that the argument list has ended and the lambda has begun. In other words, times(-1) would make Auto Lambda mandatory for that command — the => { form could no longer be supported alongside it. Because this is a real forward-compatibility break (not just an additive convenience), it is explicitly out of scope for this proposal and is left as a possible, separately-designed extension.
-
Should IDE tooling (gopls-equivalent) offer a quick-fix to convert between the => form and the Auto Lambda form? This is an editor-tooling concern outside the scope of this language proposal.
XGo already supports a family of "command-style" calls that take a trailing lambda expression, written with an explicit
=>:This works uniformly across all XGo code today. However, in certain classfiles — most notably game/animation-oriented ones such as
spx— this pattern is extremely common, and the=> {boilerplate adds visual noise to what is conceptually closer to a native control-flow construct (a loop, an event handler, etc.).We want classfile authors to be able to opt in to a terser form for specific commands, so the same code can instead be written as:
We call this feature Auto Lambda: the trailing
{ ... }block is automatically recognized as a lambda argument, without requiring=>.Auto Lambda is strictly a parser-level sugar. It introduces no new runtime semantics and no new AST node kinds — it only changes how a specific, explicitly opted-in set of command calls may be spelled.
Non-Goals
=>. Plain function calls, and command calls in classfiles that have not enabled Auto Lambda for that command, must continue to use the existingcmd args..., => { ... }form.Activating Auto Lambda
A classfile enables Auto Lambda for a specific set of commands by adding an
autolambdadirective to itsgox.mod:Each entry has the form
name(n), where:nameis the command's identifier (the function/method name as called in XGo source, e.g.times,forEver,onKey).nis the number of non-lambda arguments the command takes, i.e. the arguments that appear before the (now implicit) trailing lambda.For example:
times(1)times 3, => { ... }3) before the lambdaforEver(0)forEver => { ... }onKey(1)onKey KeySpace, => { ... }KeySpace) before the lambdaA single
gox.modmay declare Auto Lambda for multiple commands in one directive, as shown above, or across multipleautolambdalines.Parsing Rules
When the parser is processing source belonging to a classfile whose
gox.moddeclaresautolambdafor a commandname(n), it applies the following rule while parsing a command-style call toname:namefollowed by exactlyncomma-separated arguments, exactly as it would for a normal command call.narguments, instead of requiring, => {, the parser also accepts a bare{directly starting a block.{is found in that position, it is parsed as the lambda body and treated exactly as if the source had written, => {(or, whenn == 0,=> {) at that point.=>form remains valid at all times, even for commands that have Auto Lambda enabled. Auto Lambda only adds an alternative spelling; it never removes the original one.If a command with Auto Lambda enabled is called with a different number of leading arguments than declared (e.g.
timescalled with 2 arguments before a bare{), the bare{is not treated as an implicit lambda; ordinary parsing rules apply, and this will surface as a normal type/argument-count error at the call site, not as an Auto Lambda-specific error. This keeps the feature's parsing rule simple: it only fires when the argument count exactly matches whatgox.moddeclares.Why this doesn't need
=>to disambiguateOutside of Auto Lambda,
=>exists so the parser can tell a command call with a trailing lambda apart from a command call statement followed by an unrelated block statement, or from other constructs that could start with{. Because Auto Lambda is enabled per command name, and only inside classfiles that explicitly opt in, the parser already knows — at the moment it starts parsing a call totimes,forEver, oronKeyin such a classfile — that a bare{immediately following the declared argument count can only mean one thing: the lambda body. There is no ordinary XGo statement form where a command call is immediately followed by a{block that means something else, so no ambiguity is introduced.Under the Hood
Auto Lambda does not change code generation, type checking, or overload resolution. Once parsed,
times 3 { ... }produces the sameLambdaExprnode — with the same argument list and body — astimes 3, => { ... }would have.The one AST-level change is a new boolean field on
LambdaExpr:When the parser desugars a bare
{ ... }into aLambdaExprbecause=>was omitted (i.e. the Auto Lambda form was used), it setsAutoLambdatotrue. When the source uses the explicit=> { ... }form,AutoLambdais left as its zero value,false. In both cases the rest of the node (the lambda's parameters and body) is populated identically.Consequently:
goxcode generation do not need to branch onAutoLambdaat all — the field exists purely so that source position/spelling information isn't lost, not because the two forms compile differently.AutoLambdaif they need to preserve or reproduce the original spelling (for example, a formatter that wants to printtimes 3 { ... }back out instead of expanding it totimes 3, => { ... }), but no compiler stage is required to treat the two forms differently.Example:
spx-style Classfilegox.mod:Usage:
Note the last call still uses the explicit
=>form — this remains valid even thoughonKeyhas Auto Lambda enabled, since Auto Lambda only adds an alternate, terser spelling rather than replacing the existing one.Compatibility
gox.moddoes not declareautolambdafor a given command, are entirely unaffected: such commands must continue to use=>as they do today.autolambdafor a command ingox.modis purely additive and backward compatible — all existing call sites using=>keep compiling unchanged.nis declared explicitly ingox.modrather than inferred from a function signature, Auto Lambda works uniformly for overloaded commands, as long as every overload that is meant to support Auto Lambda shares the same leading-argument countn.Open Questions
Should
gox.modallow declaring Auto Lambda for a command with more than one possible leading-argument count (e.g. supporting bothtimes(1)and a futuretimes(2)overload)? The current proposal assumes a singlenper command name; extending this is left for future work if a concrete use case arises.One possible future direction is a special value
times(-1), meaning "support a variable number of leading arguments" rather than a fixed count. However, this cannot be made to work while also preserving backward compatibility with the explicit=> {form: with a fixedn, the parser knows exactly where the argument list ends and can therefore accept either=>or a bare{at that boundary; with a variadic argument count, the parser has no fixed boundary to look for, so a bare{becomes the only reliable signal that the argument list has ended and the lambda has begun. In other words,times(-1)would make Auto Lambda mandatory for that command — the=> {form could no longer be supported alongside it. Because this is a real forward-compatibility break (not just an additive convenience), it is explicitly out of scope for this proposal and is left as a possible, separately-designed extension.Should IDE tooling (gopls-equivalent) offer a quick-fix to convert between the
=>form and the Auto Lambda form? This is an editor-tooling concern outside the scope of this language proposal.