Repository navigation
Lambdas & functions as entities #495
Description
Lambdas and function entities
This was planned for 1.6, but may be implemented much sooner (as soon as it's clear what we are going to do).
The plan consists of two key points: apply node and lambda node.
Apply node
This node indicates that something is applied to something.
Declaration
record Application(Entity Expression, LList<Entity> Arguments);LList is a linked list from Honk# 1.0.3+.
Syntax
It will have syntax apply(expr, ...), for example,
apply(a, x)
apply(a, x, y, z)
apply(1 + sqrt(y), x)
apply(y + y, y + y)
apply(y + y, y + y, apply(y + y, y + y))
etc.
With implicit parsing disabled by default, it will create an apply node for cases when an expression follows another expression, for example,
x x
a 5
sin x
f a b
but with priority higher than operators:
f a b = apply(apply(f, a), b)
f a b * 2 = apply(apply(f, a), b) * 2
The priority is higher than all others except for Leaf and is defined as
Func = 80 | NumericalOperation,
(this existed before for functions, no additional is added for application)
Comma-separated arguments are still allowed:
f a b
is the same as
f(a, b)
Relaxing syntax for calling built-in functions
Currently you cannot have spaces between parentheses when calling a built-in function: sin(x) is valid,
but sin (x) will be interpreted as sin * x.
Now it won't be so with implicit parsing disabled. sin (x) will be interpreted as sin(x).
Moreover, there will be no need to set parentheses. sin x will be equal to sin(x). In this case, operator has a lower priority (that is, sin x * x is (sin x) * x).
Lambda node
This node is for declaration lambdas. All logic about lambdas is going to be borrowed from lambda-calculus.
Declaration
record Lambda(Variable Var, Entity Body);Syntax
List of variables separated by space, followed by =>, followed by the lambda's body. For example,
a => a + 3
a => a + b
a b => a + b (the same as a => b => a + b)
a b c => 3 (the same as a => b => c => 3)
a 3 => 3 Invalid
As synonym, lambda can be used:
lambda(x, x + 3)
lambda(x, y, x + 3)
lambda() // error
lambda(x) // error
Parameters syntax
Identifiers (variables), separated by space, before =>, substitute lambdas' parameters, so it reads from the right to the left. A non-variable interrupts it.
2 + a => 3 (the same as 2 + (a => 3), will be parsed to the left starting from =>)
a b quack c => 3
a + b * c => a + b (the same as a + b * (c => a + b))
The priority of => will be lower than provided (and hence, lower than any other operator and construction).
Logic
When simplifying, apply of lambda and something will result in lambda's body substituted with the argument:
(a b => a + b) 4
becomes
b => a + 4
When substituting, lambda may be alpha-converted on demand. For example:
(a b => a + b) b
will become
c => b + c
Derivative, integral, limit do not affect lambdas, if the latter are passed as parameters.
Currying of built-in functions is allowed via lambdas:
derivative (x + a)
is inner simplified to
y => derivative (x + a) y
Differentiation and integration
Derivative and integral of an applied function now can be reduced by corresponding rules, for example:
derivative(f (x ^ 2), x)
will be reduced to
derivative(f (x ^ 2), x ^ 2) * derivative(x ^ 2, x)
Breaking changes
By default, implicit parsing is enabled currently. To enable proper application syntax, it will be disabled (but remains as an option in the settings).
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
Application and lambda added
Merge pull request #496 from asc-community/impl-495
[-][WIP] Lambdas & functions as entities[/-][+]Lambdas & functions as entities[/+]Rafael-SOWNet commentedon Aug 16, 2026
Half of this is implemented and half is not, and the half that is not turns out to be the same job as #286. Measured on
masterata45a7256.Done — both nodes exist and reduce
apply(f, x)Applicationapply(a, x, y, z)Applicationlambda(x, x^2)Lambdaapply(lambda(x, x^2), 3).Simplify()9apply(apply(lambda(x, lambda(y, x + y)), 1), 2).Simplify()3— currying worksSo the two "key points" in the body — the
applynode and thelambdanode — are there, with beta reduction, including the curried form the plan spells out asa b => a + bbeinga => b => a + b.Not done — the whole syntax layer
a => a + 3UnhandledParseExceptiona b => a + bUnhandledParseExceptionf a b=apply(apply(f, a), b)f * a * bsin (x)with a spacesin * xsin xwithout parenthesessin * xThe connection worth making
The plan's juxtaposition rule — "with implicit parsing disabled by default, it will create an apply node for cases when an expression follows another expression" — is the same change #286 needs. There,
f(x) * g(x)parses asf * x * g * xand differentiates to2 * f * g * x: a silent wrong answer for anyone reaching for generic functions.So these are not two features. The nodes this issue asks for already exist; what both issues are waiting on is one decision about implicit multiplication, and the measurement on #286 says the multi-argument form
f(x, y)is already a parse error and therefore free to claim at zero compatibility cost.I would suggest re-scoping this issue to the syntax layer alone — the nodes half is finished and its checklist reads as though nothing has happened — and treating #286 as the same work rather than a neighbour.
A lambda is written with an arrow as well as a call (#1151)
Rafael-SOWNet commentedon Sep 3, 2026
The lambda arrow is done, in
#1151 — the half of this plan's syntax
that costs nothing.
=followed by>was not a token and not a parse, so no reading of any valid input changed.>=,->(implication, not a lambda arrow),=,a bas multiplication andx2as a power areall untouched and pinned by tests.
Two things had to be got right and are worth recording, since both are invisible from the plan:
The grammar is left-factored. Written the obvious way —
names+ '=>' body | expression— bothalternatives begin with a name and stay viable through a second one, juxtaposition being
multiplication, so
a b => a + bwas decided as a product before the arrow was ever reached andcame back
mismatched input '=>'. Sharing the left side leaves one decision, taken on the tokenafter it, and the parameters are read back out of the product it parsed as.
icannot arrive as a name token. It lexes as the imaginary unit, so a rule matchingVARIABLEcould never accepti => i + 1— whilelambda(i, i + 1)has always worked, becausethe call form reads its parameters through
Binding(#976). The left-factored form gets this forfree by doing the same, and the two spellings now agree.
The arrow is read, not printed: a lambda still prints as
lambda(x, x + 1), which is whatkeeps the round trip the printed form promises.
What is not done, and why it is one decision rather than three
The rest of this plan's syntax:
f a b=apply(apply(f, a), b)f * a * bsin (x)with a spacesin * xsin xwithout bracketssin * xAll three are the juxtaposition rule — "with implicit parsing disabled by default, it will create
an apply node for cases when an expression follows another expression" — and that is the same
change #286 needs, where
f(x) * g(x)parses as
f * x * g * xand differentiates to2 * f * g * x, a silent wrong answer. It is adeliberate compatibility break on implicit multiplication and I have not chosen it; the trade-off
is measured on #286, including that the multi-argument form
f(x, y)is already a parse error andtherefore free.
Worth knowing before anyone starts: differentiating an
applyof two or more arguments used totake the process down with a stack overflow
(#1149, fixed in
#1150). It went unhit precisely because
f(x, y)does not parse, so whichever way the juxtaposition decision goes would have met it first.