Skip to content

A lambda is written with an arrow as well as a call (#495) - #1151

Merged
Rafael-SOWNet merged 3 commits into
masterfrom
lambda-arrow-syntax
Sep 3, 2026
Merged

Rafael-SOWNet merged 3 commits into
masterfrom
lambda-arrow-syntax

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

a => a + 3 was a parse error. It is now the same entity as lambda(a, a + 3), and several
parameters are the curried form #495's plan specifies.

"a => a + 3".ToEntity()                          was  UnhandledParseException
                                                 is   lambda(a, a + 3)
"a b => a + b".ToEntity()                        is   lambda(a, lambda(b, a + b))
"apply(apply(a b => a + b, 1), 2)".Simplify()    is   3

The nodes were never missing

Lambda, Application, beta reduction and currying are all already in the library — #1137's ODE
solver is built on them, and apply(apply(lambda(x, lambda(y, x + y)), 1), 2) has simplified to
3 throughout. What the plan describes and the library did not have is the syntax.

This is the half of that syntax which costs nothing: = followed by > was not a token and not a
parse, so no reading of any valid input changes. The other half — f a b for
apply(apply(f, a), b), sin x without brackets, sin (x) with a space — changes what
juxtaposition means. That is the same decision #286 is about, it is a real compatibility break,
and it is not in here.

Two things that had to be got right

The grammar is left-factored, and that is not a style choice. Written the obvious way —

expression : names+ '=>' body | provided_expression ;

— both alternatives begin with a name and stay viable through a second one, juxtaposition being
multiplication. So a b => a + b was decided as a product before the arrow was ever reached, and
came back mismatched input '=>' expecting <EOF>. Sharing the left side leaves one decision,
taken on the token after it, and the parameters are read back out of the product it parsed as.

i cannot arrive as a name token. It lexes as the imaginary unit, so a rule matching
VARIABLE tokens could never accept i => i + 1 — while lambda(i, i + 1) has always worked,
because the call form reads its parameters through Binding (#976). The left-factored form gets
this for free by doing the same, and the two spellings now agree. Asserted.

What is refused, and what is untouched

Every parameter must be a name, which is what the plan says:

input result
a 3 => 3 raises — the plan writes this down as invalid
2 => 3, x + 1 => 2, sin(x) => 2 raise

Those raised before too, as UnhandledParseException; they now raise
InvalidArgumentParseException. Still invalid, differently named — recorded in
BREAKING-CHANGES.md, since code catching by type will not catch the new one.

Untouched, and pinned by tests: >=, <=, >, <, =, -> (implication, not a lambda
arrow), a b as multiplication, x2 as a power.

The arrow is read, not printed. A lambda still prints as lambda(x, x + 1), which is what
keeps the round trip the printed form promises: several spellings may be read, exactly one is
printed. Asserted, including that the printed form reads back equal.

On regenerating the parser

Done the way ImproveParser.md sets out. Per AGENTS.md, I regenerated the unmodified grammar
first and confirmed its diff was empty
— it was, once the post-processor had run — so what is
committed here is the rule and not a toolchain version.

Syntax.md is updated in three places: the round-trip contract, the structural-node list, and
the line that says -> is not a lambda arrow.

Verification

Full suite 9386 passed, 0 failed, 14 skipped. 30 new tests, each asserting the arrow form
against its lambda(...) spelling rather than against a printed string — the point being that the
two produce the same entity.

Part of #495 — the rest of that plan's syntax is the juxtaposition change, deliberately not here.

Rafael-SOWNet and others added 3 commits September 3, 2026 00:31
`a => a + 3` was a parse error and is now the same entity as
`lambda(a, a + 3)`. Several parameters are the curried form #495's plan
specifies: `a b => a + b` is `a => b => a + b`, which is
`lambda(a, b, a + b)`.

The nodes were never missing. `Lambda`, `Application`, beta reduction and
currying are all in the library already, and #1137's ODE solver is built on
them; what that plan describes and the library did not have is the syntax.
This is the half of it that costs nothing -- `=` followed by `>` was not a
token and not a parse, so no reading of any valid input changes. The other
half (`f a b`, `sin x`, `sin (x)`) changes what juxtaposition means, which is
the decision #286 is about, and is not free.

Written by sharing the left side rather than as `names+ '=>' body | expression`.
Both alternatives of that begin with a name and stay viable through a second
one, juxtaposition being multiplication, so `a b => a + b` was decided as a
product before the arrow was reached and came back "mismatched input '=>'".
Sharing leaves one decision, taken on the token after the left side, and the
parameters are read back out of the product it parsed as.

That also settles the parameter that cannot arrive as a name token: `i` lexes
as the imaginary unit, so `i => i + 1` had to be read the way `lambda(i, i + 1)`
is read -- through `Binding` -- and the two now agree. #976.

A parameter that is not a name is refused, as the plan says: `a 3 => 3`,
`2 => 3` and `x + 1 => 2` raise. They raised before too, under a different
name -- `UnhandledParseException` where they now raise
`InvalidArgumentParseException` -- which is recorded in BREAKING-CHANGES.

The arrow is read, not printed. A lambda still prints as `lambda(x, x + 1)`,
which is what keeps the round trip the printed form promises.

The grammar was regenerated the way ImproveParser.md sets out. The unmodified
grammar was regenerated first and its diff confirmed empty, so what is
committed here is the rule and not a toolchain version. Syntax.md updated in
three places.

Full suite 9386 passed, 0 failed.

Part of #495.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit e3c6b76 into master Sep 3, 2026
32 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the lambda-arrow-syntax branch September 3, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant