Skip to content

Lambdas & functions as entities #495

Open

Description

@WhiteBlackGoose

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).

Activity

  1. added 2 commits that reference this issue on Aug 25, 2021
  2. added
    Design documentFor issues representing detailed design of new API or feature
    and removed on Sep 5, 2021
  3. changed the title [-][WIP] Lambdas & functions as entities[/-] [+]Lambdas & functions as entities[/+] on Sep 5, 2021
  4. added this to the 1.4 milestone on Sep 7, 2021
  5. added
    AcceptedFor proposals, which were approved and will be implemented
    on Aug 11, 2026
  6. Rafael-SOWNet commented on Aug 16, 2026

    @Rafael-SOWNet
    Member

    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 master at a45a7256.

    Done — both nodes exist and reduce

    apply(f, x) Application
    apply(a, x, y, z) Application
    lambda(x, x^2) Lambda
    apply(lambda(x, x^2), 3).Simplify() 9
    apply(apply(lambda(x, lambda(y, x + y)), 1), 2).Simplify() 3 — currying works

    So the two "key points" in the body — the apply node and the lambda node — are there, with beta reduction, including the curried form the plan spells out as a b => a + b being a => b => a + b.

    Not done — the whole syntax layer

    plan today
    a => a + 3 UnhandledParseException
    a b => a + b UnhandledParseException
    f a b = apply(apply(f, a), b) f * a * b
    sin (x) with a space sin * x
    sin x without parentheses sin * x

    The 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 as f * x * g * x and differentiates to 2 * 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.

  7. Rafael-SOWNet commented on Sep 3, 2026

    @Rafael-SOWNet
    Member

    The lambda arrow is done, in
    #1151 — the half of this plan's syntax
    that costs nothing.

    "a => a + 3"       is  lambda(a, a + 3)
    "a b => a + b"     is  lambda(a, lambda(b, a + b))     -- the plan's `a => b => a + b`
    "a b c => 3"       is  lambda(a, lambda(b, lambda(c, 3)))
    "a 3 => 3"         raises                              -- the plan writes this down as invalid
    

    = followed by > was not a token and not a parse, so no reading of any valid input changed.
    >=, -> (implication, not a lambda arrow), =, a b as multiplication and x2 as a power are
    all 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 — 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 '=>'. 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 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.

    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.

    What is not done, and why it is one decision rather than three

    The rest of this plan's syntax:

    plan today
    f a b = apply(apply(f, a), b) f * a * b
    sin (x) with a space sin * x
    sin x without brackets sin * x

    All 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 * x and differentiates to 2 * f * g * x, a silent wrong answer. It is a
    deliberate 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 and
    therefore free.

    Worth knowing before anyone starts: differentiating an apply of two or more arguments used to
    take 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.

  8. modified the milestones: 1.5, 2.7.0 on Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    AcceptedFor proposals, which were approved and will be implementedDesign documentFor issues representing detailed design of new API or feature

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions