Skip to content

Proposal: Auto Lambda #2828

Description

@xushiwei

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:

  1. Parse name followed by exactly n comma-separated arguments, exactly as it would for a normal command call.
  2. After those n arguments, instead of requiring , => {, the parser also accepts a bare { directly starting a block.
  3. 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.
  4. 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:

AutoLambda  bool

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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions