Files
odfstudio/sc/README.Lambda.md
Ben Urban 5776da7d06 tdf#138749: Enabled constructing a LAMBDA with no parameters
The sequence for LAMBDA in the RPN code is now:
[0] ocLambda, svJump
[1] ocPush, svStringName (parameter name)
[2] ocSep, svSep
(Repeat [1] and [2] for each parameter)
[3] (body code)
[4] ocClose, svSep
[5] (code after LAMBDA expression)

Jumps in the ocLambda point to [0], each [2], and [4], which means
there are two more jumps than parameters.

For example:
=LAMBDA(x;y;x+y)(3;4)
generates the following RPN code:
index opcode  type (data)
 [0] ocLambda svJump ({4,0,2,4,8})
 [1] ocPush   svStringName ("x")
 [2] ocSep    svSep
 [3] ocPush   svStringName ("y")
 [4] ocSep    svSep
 [5] ocPush   svStringName ("x")
 [6] ocPush   svStringName ("y")
 [7] ocAdd    svByte
 [8] ocClose  svSep
 [9] ocPush   svDouble (3.0)
[10] ocPush   svDouble (4.0)
[11] ocCall   svByte (3)
The first parameter is in [1], the second is in [3], the body is
in [5]-[7], and [9] is where the code resumes after the LAMBDA.

Change-Id: I4dc871893f47ebbd4dad9cc02ac3efe0ca6ca883
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207973
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-18 18:08:25 +02:00

5.0 KiB

LAMBDA

The LAMBDA operator builds a callable function out of a Calc formula.

Using LAMBDA

  • LAMBDA expects a list of parameter names, followed by an expression.

  • All parameter names must be valid identifiers, and must not match any names already defined by Calc, nor any possible cell references. Identifiers are not case-sensitive. Identifiers must start with a letter, and may contain only letters, numbers, and underscores (_).

  • You may specify no parameters, if you want the function to operate the same way every time it is called (this may be useful if its expression includes volatile operators such as RAND or TIME).

  • The result of a LAMBDA is a function. Don't expect that to show up in the spreadsheet in any useful way! You must call the function to use it.

  • Call a LAMBDA function just like you call a built-in function: put the name of the function (either a named range, a cell reference, or the LAMBDA expression itself) into your formula before a pair of parentheses containing the arguments you want to pass to the function.

  • If you are nesting one LAMBDA inside another, you may use the same parameter name in both (if you really want - this can easily make things hard to read!). The innermost LAMBDA specifying a parameter of a given name applies at each level of scope. This also works with LET.

  • A function can call itself (this is called recursion), but you must be able to refer to it from within the function. For example, if cell A1 is a LAMBDA formula, the body of the LAMBDA can call itself by referring to A1. You can name your LAMBDA cell to make this easier to read.

  • If you want your LAMBDA function to permit optional parameters, enclose those parameter names (in the parameter list, not in the body) in brackets, like this:

=LAMBDA(name1, name2, [name3], [name4], expression)

  • ISOMITTED can be used within a LAMBDA body to detect whether an optional parameter was provided or omitted.

  • Optional parameters must not appear before any required parameters in the parameter list. Your parameters can appear in any order inside the body.

Examples

(This form is permitted, but not recommended, as it is difficult to read.)

A1: =LAMBDA(a,b,a+b)(3*5,7/(6+4))

A1 will show 15.7 (=(3*5)+(7/(6+4)) )


A1: =LAMBDA(a,b,a+b)

B1: =A1(3*5, 7/(6+4))

B1 will show 15.7


If MY_SUM is A1:

A1: =LAMBDA(a,b,a+b)

B1: =MY_SUM(3*5,7/(6+4))

B1 will show 15.7


If FACT is A1:

A1: =LAMBDA(n,IF(n>1,n*FACT(n-1),1))

B1: =FACT(5)

B1 will show 120 (FACT duplicates the functionality of the built-in FACTORIAL function, but is less efficient)

How it Works

  • The ocLambda OpCode corresponds to the LAMBDA operator

  • ocLambda is set up as a jump OpCode, meaning that it has special rules for what parameters are evaluated, and when.

  • The first stage of the compiler tokenizes the elements of the LAMBDA expression, like any other. It also looks for the lambda parameters, and interns them as identifiers.

  • The second stage of the compiler sets up the jump table with the parameters and body. It also looks for any parameters between ocTableRefOpen/ocTableRefClose (which correspond to brackets), and sets mIsOptional for those parameters.

  • The ocLambda is built in the compiled code as a sequence consisting of the ocLambda token itself, then the parameter names (svStringName) separated by ocSep, followed by another ocSep, then the lambda body, with ocClose at the end. The jump table points to the ocLambda, each ocSep, and the ocClose, which allows the interpreter to find each parameter name (the ocLambda and ocSeps except the last), the body (the last ocSep), and the position where execution continues (ocClose).

  • When interpreted, ocLambda causes a ScFormulaFunction to be built using the contents of the jump table. At this point all parameters are counted, and that information is passed on to the next step.

  • ScFormulaFunction's constructor clones the lambda body and saves the interpreter's contextual information, then locates each use of each parameter in the scope of that identifier in the body and makes note of where it is.

  • When a ScFormulaFunction is called via ScInterpreter::ScCall, it builds a new interpreter out of the saved context, performs the replacements specified, and executes the interpreter to obtain a result. This means that the LAMBDA body is executed in the same context that the LAMBDA appeared in.

  • ocLambda is itself nonvolatile, and overrides any volatility in its body, since the LAMBDA never produces a different callable, no matter what functions appear in the body.

  • The presence of an enclosing LAMBDA also prevents circular references from being a problem, as the body of a LAMBDA is not executed until the cell containing it already has a value. This allows recursion.

  • LAMBDA does not currently check for infinite recursion; avoid recursive calls that can cause such a loop.