Skip to main content

Functions

Functions are first-class values in the Compute Engine. This means that functions can be passed as arguments to other functions, returned from functions, and assigned to variables.

The standard library can be extended with your own functions.

TermDefinition
Function ExpressionAn expression representing a function application where a function (or operator) is evaluated with arguments (the arguments are applied to the function). For example ["Add", 1, 2] applies the arguments 1 and 2 to the operator Add.
Function SignatureA type describing the function's inputs and outputs, e.g. (real, integer) -> boolean
Function LiteralA first-class function value, defined using a construct like ["Function", body, params...], which may or may not capture variables from its lexical scope.
Shorthand Function LiteralA compact way to write a function literal using placeholders (e.g. _ or _2) instead of explicitly listing parameters, e.g. ["Add", "_", 1] desugared to ["Function", ["Add", "_", 1], "_"].
ClosureA function literal that captures one or more free variables from its defining lexical scope: the function reads the binding of the variable where it was defined, with the value that binding holds when the function is called. The capture happens by simply referencing the variables in the function body.
Anonymous FunctionA function that is not bound to a symbol, often used as an argument to other functions.

Function Literals​

A function literal is a first-class function value, defined using a ["Function"] expression. It can be passed as an argument to other functions, returned from a function, or assigned to a variable.

The ["Function"] expression takes a body and a list of parameters.

["Sum", ["Function", ["Multiply", "x", 2], "x"]]

To specify a function literal with LaTeX use the \mapsto command:

x \mapsto 2x
$$$x \mapsto 2x$$
["Function", ["Multiply", "x", 2], "x"]
(x, y) \mapsto 2x + y
$$$(x, y) \mapsto 2x + y$$
["Function", ["Add", ["Multiply", "x", 2], "y"], "x", "y"]

A parameter may also be a tuple pattern: a ["Tuple", …] of parameter names (or _ to skip a position, or a nested pattern) in a parameter position. It is still one parameter — it takes one argument, which must be a tuple of the pattern's shape, and binds a name to each component. In Epsil it is written with a second pair of parentheses, ((p, q)) => p + q, as opposed to the two-parameter (p, q) => p + q.

["Map",
["Function", ["Add", "p", "q"], ["Tuple", "p", "q"]],
["List", ["Tuple", 1, 2], ["Tuple", 3, 4]]]
// ➔ ["List", 3, 7]

An argument that is not a tuple of that shape yields the same incompatible-type error value a destructuring ["Declare", ["Tuple", …], …] produces. Function literals with a tuple-pattern parameter are interpreted; the compile targets decline them rather than emit code that binds the wrong names.

The examples in this section define functions as a simple expression, but function literals can include more complex control structures, including blocks, local variables, loops and conditionals.

For example, here's a simple "clamp" function, using a ["Block"] expression.

["Function",
["Block",
["Assign", "x", ["Max", "x", "min"]],
["Min", "x", "max"]
],
"x", "min", "max"
]

Shorthand Function Literals​

A shorthand function literal is a compact way to write a function literal without explicitly listing parameters.

A shorthand function literal can use either wildcards (_, _2, etc...) or unknowns (symbols that have no value) as implicit parameters.

The shorthand function literal is desugared to a function literal.

For example the shorthand function literal ["Multiply", "_", 2] is desugared to ["Function", ["Multiply", "_", 2], "_"].

To use wildcards in LaTeX, they must be wrapped with an \operatorname command except for \_.

\_ + \operatorname{\_2}
$$$\_ + \operatorname{\_2}$$
["Add", "_", "_2"]

The shorthand function literal ["Multiply", "x", 2] which uses the unknown x is equivalent to the function literal ["Function", ["Multiply", "x", 2], "x"]. When using this form, make sure that the symbol x is not defined in the current scope.

An expression with no wildcard and no unknown is a constant function: ["Apply", 3, 5] evaluates to 3.

The unknowns of an expression become its parameters only when the expression is known not to be a function. An expression whose type is unknown is kept as a function value instead, and is applied once its value is known. This is the case for a call of a function that has no definition yet: in ["Apply", ["g", "f"], "x"] the callee g(f) is not read as the function f ↦ g(f), because g(f) may itself be a function. The application stays ["Apply", ["g", "f"], "x"]. Once g has a value, for example ["Function", ["Add", "a", "b"], "a", "b"], it evaluates to f + x: g(f) is then g applied to its first argument, a function of one argument.

To apply a function with an unknown result type as a shorthand, use a wildcard: ["Map", ["f", "_"], "xs"] maps f over xs whether or not f is defined.

A symbol which is the name of an operator (for example Sin) is also a valid function literal shorthand.

This expression will apply the Sin function to the elements of xs.

["Map", "Sin", "xs"]

It is equivalent to ["Map", ["Sin", "_"], "xs"] which is desugared to ["Map", ["Function", ["Sin", "_"], "_"], "xs"].

Anonymous Functions​

A function that is not bound to a symbol is called an anonymous function.

Anonymous functions are frequently used as arguments to other functions.

In the example below, the second argument of the Map function is an anonymous function expressed as a function literal that multiplies its argument by 2.

["Map", ["Function", ["Multiply", "x", 2], "x"], "xs"]

The same function can be expressed using a shorthand function literal, which uses _ as a wildcard for the parameter.

["Map", ["Multiply", "_", 2], "xs"]

Evaluating a Function Literal​

To apply a function literal to some arguments use an ["Apply"] expression.

["Apply", ["Function", ["Add", 2, "x"], "x"], 11]
// ➔ 13

["Apply", ["Add", 2, "_"], 4]
// ➔ 6

["Apply", "Power", 2, 3]
// ➔ 8

The first argument of Apply is a function literal. The rest of the arguments are the arguments that will be applied to the function literal.

Broadcasting Over Collections​

When a user-defined function with scalar parameter types is applied to a finite indexed collection, the call is broadcast elementwise: the function is applied to each element and the results are returned as a new List.

ce.assign('f', ce.parse('x \\mapsto x^2 + 1'));
ce.expr(['f', ['List', 1, 2, 3]]).evaluate();
// ➔ ["List", 2, 5, 10]

Multi-argument functions broadcast with zip semantics. Scalar arguments are repeated for each element of the collection argument(s):

ce.assign('h', ce.parse('(x, y) \\mapsto x + y'));

// list + scalar
ce.expr(['h', ['List', 1, 2, 3], 10]).evaluate();
// ➔ ["List", 11, 12, 13]

// list + list
ce.expr(['h', ['List', 1, 2, 3], ['List', 10, 20, 30]]).evaluate();
// ➔ ["List", 11, 22, 33]

Broadcasting also applies to lazy collections like Range:

ce.assign('sq', ce.parse('x \\mapsto x^2'));
ce.expr(['sq', ['Range', 1, 4]]).evaluate();
// ➔ ["List", 1, 4, 9, 16]

Broadcasting is decided by the evaluated argument, not its written form: an argument that only evaluates to a collection — e.g. a call to a list-returning function — broadcasts too, whatever the body of the applied function is:

ce.assign('lst', ce.parse('n \\mapsto \\lbrack n, -n \\rbrack'));
ce.assign('k', ce.parse('x \\mapsto \\begin{cases} 1 & x > 0 \\\\ -1 & x \\leq 0 \\end{cases}'));

ce.expr(['k', ['lst', 3]]).evaluate(); // lst(3) ➔ [3, -3]
// ➔ ["List", 1, -1]

The static type of an application mirrors this: applied to a visible collection, the result types list<R>; applied to an argument that may be a collection (e.g. an unknown-return call), it types broadcastable<R>. For a function literal, R is the type of the body with each parameter typed as the element the call maps over, so a parameter that flows into the result unchanged does not read unknown:

ce.parse('f(x, y, z) := (-y, z, x)').evaluate();
ce.box('f').type;
// ➔ (unknown, unknown, unknown) -> tuple<number, unknown, unknown>

ce.parse('f([1, 2], [3, 4], [5, 6])').type;
// ➔ list<tuple<integer, integer, integer>>

For a function with a declared signature, R is the declared return type (g: (number) -> integer applied to [1, 2] types vector<integer^2>, a list of integers). See the broadcastable type in the Types guide.

Tuple Arguments​

A function whose parameter is declared with a scalar type (a number type or boolean) also maps over a tuple argument, as Sin((a, b)) is (sin(a), sin(b)). The result is a tuple, typed tuple<…> with one component type per result. A component that does not match the declared type is an error in its own cell.

ce.assign('g', ce.parse('(u: \\mathrm{real}) \\mapsto 2u'));
ce.parse('g((a, b))').evaluate();
// ➔ ["Tuple", ["Multiply", 2, "a"], ["Multiply", 2, "b"]]

ce.parse('g((a, b))').type;
// ➔ tuple<real, real>

// A list of tuples maps over the list, then over each tuple
ce.expr(['g', ['List', ['Tuple', 1, 2], ['Tuple', 3, 4]]]).evaluate();
// ➔ ["List", ["Tuple", 2, 4], ["Tuple", 6, 8]]

A parameter with no declared type, or declared string, binds the tuple whole: Apply(u ↦ 7, (a, b)) is 7. The compiled code gives the same tuple: an array on the javascript target, and a vecN on the GLSL and WGSL targets.

Opting Out​

To author a function that intentionally consumes a list (rather than broadcasting elementwise over it), declare the parameter type explicitly as list<…> or another collection type. The function will then receive the collection as a single argument.

ce.declare('len', { signature: '(list<number>) -> integer' });
ce.assign('len', ce.parse('L \\mapsto \\operatorname{length}(L)'));

ce.expr(['len', ['List', 1, 2, 3]]).evaluate();
// ➔ 3 (no broadcasting; len receives the list as a whole)

For lambdas defined with \mapsto without an explicit signature, the inferred parameter type is unknown, which is treated as scalar — so they broadcast by default, unless the body shows that the parameter is a collection.

One such case needs no annotation. When the body hands the parameter to an operator that consumes a whole collection — Mean, Median, Variance, StandardDeviation, Mode, Quartiles, Max, Min, Sum, Product, GCD, LCM, Flatten, ListFrom, SetFrom or TupleFrom — a list argument binds whole. The parameter is followed through element-wise operators (mean(v^2), sin(v)) and through a local of the body assigned from it.

ce.assign('h', ce.parse('v \\mapsto \\operatorname{mean}(v^2)'));
ce.expr(['h', ['List', 1, 2, 3]]).evaluate();
// ➔ 14/3 (the mean of [1, 4, 9]; not [1, 4, 9])

ce.assign('s', ce.box(['Function', ['Sum', ['Power', 'v', 2]], 'v']));
ce.expr(['s', ['List', 1, 2, 3]]).evaluate();
// ➔ 14

ce.assign('mx', ce.parse('v \\mapsto \\max(v)'));
ce.expr(['mx', ['List', 1, 2, 3]]).evaluate();
// ➔ 3

// A body with no such operator still broadcasts
ce.assign('q', ce.parse('v \\mapsto v^2 + 1'));
ce.expr(['q', ['List', 1, 2, 3]]).evaluate();
// ➔ ["List", 2, 5, 10]

A scalar argument gives the scalar value (h(2) is 4). Compiled JavaScript follows the same rule.

A function that passes a collection to itself at a parameter's position also receives that parameter whole: with f(n, s) = s if n == 0 else f(n - 1, [1, 2]), f(1, [0]) is [1, 2], and the parameter s is inferred as list<any>. See "When Inference Triggers" in the Types guide.

Closures​

A closure is a function literal that captures one or more free variables from its defining lexical scope. The capture happens by simply referencing the variables in the function body. For example, the following function literal captures the variable a from its lexical scope:

["Function", ["Add", "a", "_"], "_"]

The function captures the binding of a where it is defined, not the value of a at that time. When the function is applied, it reads the value that this binding holds then, so an assignment to the same a made after the definition is visible to the function:

["Block",
["Declare", "a", "integer"],
["Assign", "a", 10],
["Assign", "f", ["Function", ["Add", "a", "_"], "_"]],
["Assign", "a", 20],
["f", 1]
]
// ➔ 21

A variable of the same name declared in another scope is a different binding, which the function does not read:

["Block"
["Assign", "f",
["Block",
["Declare", "a", "integer"],
["Assign", "a", 10],
["Function", ["Add", "a", "_"], "_"]
]
]
["Declare", "a", "integer"],
["Assign", "a", 100]
["f", 1]
]
// ➔ 1 + 10

The inner a, assigned 10, is the binding the function captured. The outer a, assigned 100, is a different variable with the same name, so the function does not read it. The same rule applies to a function called in the body: a call reads the function that its name denotes where the body is written, and a same-named function defined in another scope does not redirect it (see the Evaluation guide).

Operating on Functions​

Function(body)

Function(body, arg-1, arg-2, ...)

Create a function literal which can be used as an anonymous function.

The arg-n arguments are symbols which are the bound variables (parameters) of the function literal.

Note

The Function operator has the lazy flag set to true, which means that neither the body nor the parameters are evaluated until the function is applied to some arguments.

The body is an expression that is evaluated when the function is applied to some arguments.

To apply some arguments to a function expression, use ["Apply"].

x \mapsto 2x
$$$x \mapsto 2x$$
["Function", ["Multiply", "x", 2], "x"]
(x, y) \mapsto 2x + y
$$$(x, y) \mapsto 2x + y$$
["Function", ["Add", ["Multiply", "x", 2], "y"], "x", "y"]

Typed parameters. A parameter may be annotated with a type by writing it as a ["Typed", _symbol_, _type_] expression instead of a bare symbol. The type is a string such as {"str": "integer"} (or a type-name symbol, which is normalized to a string).

["Function", ["Add", "x", 1], ["Typed", "x", "'integer'"]]

The literal then types as a named signature — the example above has type (x: integer) -> integer — and, in strict mode, its arguments are checked against the declared types when the function is applied (see Typed).

Return type. To ascribe a return type, wrap the body in a Typed expression:

["Function", ["Typed", ["Add", "x", 1], "'integer'"], ["Typed", "x", "'integer'"]]

The annotation is authoritative: it sets the result type of the signature directly. When the function is applied, a concrete returned value (a number, a string, a character, a boolean, or a list or tuple of these) is checked against the declared result type: with g declared (integer) -> integer and the body n / 2, g(4) is 2 and g(5) is an incompatible-type error. A symbolic value, Missing, Nothing, NaN and an error value pass unchanged, and a real float passes under integer or rational, so .N() of such a function gives the float. Compiled code does not make this check. Canonicalization moves the ascription inside the body Block, wrapping its last statement, so the canonical form of the example above is:

["Function",
["Block", ["Typed", ["Add", "x", 1], "'integer'"]],
["Typed", "x", "'integer'"]]

Generic literals. A whole-signature annotation carrying a where clause makes the literal generic (see Generic Signatures). It may be written as a signature string in place of the parameter list:

["Function", ["Add", "x", "x"], "'(x: T) -> T where T: number'"]

or as a full-signature Typed marker on the body — which is also the canonical form the sugar above lowers to:

["Function",
["Block", ["Typed", ["Add", "x", "x"], "'(x: T) -> T where T: number'"]],
"x"]

The signature is the single source of truth for the parameter types: a parameter whose type mentions a type variable is erased to a bare symbol, while a ground one (["Typed", "n", "'integer'"]) keeps its annotation and is checked as usual. Each application solves the variables against the arguments, so ["Apply", _literal_, 21] above evaluates to 42 and the bound number is enforced. A where clause on an individual parameter annotation is not accepted — type variables are introduced by a whole-signature clause only.

Type annotations round-trip through MathJSON but are dropped when serializing to LaTeX (there is no LaTeX notation for them).

Assign(symbol, fn)

Assign the function literal fn to the symbol symbol.

Assign is not a pure function, as it changes the state of the Compute Engine.

The fn is a function literal, which can be created using the ["Function"] expression or the shorthand function literal.

\operatorname{double}(x) \coloneq 2x
$$$\operatorname{double}(x) \coloneq 2x$$
\operatorname{double} \coloneq x \mapsto 2x
$$$\operatorname{double} \coloneq x \mapsto 2x$$
["Assign", "double", ["Function", ["Multiply", "x", 2], "x"]]

Apply(function, expr-1, ...expr-n)

Apply a list of arguments to a function. The function is a function literal, a symbol whose value is a function literal, or an expression whose value is a function.

The expr-n arguments are the arguments of the function literal.

["Apply", ["Multiply", "_", "_"], 3]
// ➔ 9

["Apply", ["Function", ["Multiply", "x", "x"], "x"], 3]
// ➔ 9

An expression that is not a function literal is read as a shorthand function literal: ["Apply", ["Add", "x", 1], 2] is 3. An expression whose type is unknown is not: it is kept as a function value, and the application evaluates once the value is known. With g undefined, ["Apply", ["g", "f"], "x"] stays as written, and the same expression written with a compound head, [["g", "f"], "x"], is the same application. Once g is a function of two arguments, g(f) is that function applied to its first argument, and ["Apply", ["g", "f"], "x"] is g(f, x): a function applied to fewer arguments than it declares is a function of the remaining ones.

["Apply", ["Function", ["Add", "x", "y"], "x", "y"], 1]
// ➔ ["Function", ["Add", "_1", 1], "_1"]

["Apply", ["Apply", ["Function", ["Add", "x", "y"], "x", "y"], 1], 2]
// ➔ 3

A function literal applied to more arguments than it declares evaluates to an unexpected-argument error.

With LaTeX, the \lhd and \rhd commands can be used to apply a function to a single argument on the left or right respectively.

f\lhd g \lhd x
$$$f\lhd g \lhd x$$
x \rhd g \rhd f
$$$x \rhd g \rhd f$$
["Apply", "f", ["Apply", "g", "x"]]

The right-pointing form is a pipeline operator: \rhd (also \triangleright, \vartriangleright, ⊳, or the plain-text shortcut |>) feeds the expression on its left to the function on its right, and stages chain left to right.

The corresponding expression is Pipe(value, function). For example, Epsil x |> f constructs Pipe(x, f) and evaluates by applying f to x:

["Pipe", "x", "f"]
// Evaluates as ["f", "x"]

The right-hand side of a pipe must be applicable. A number, string, or boolean literal can never be applied, so piping into one is an incompatible-type error: 5 \rhd 3 errors rather than staying inert. A symbol without a definition is not an error — the pipe stays symbolic and evaluates once the symbol is defined. Note that Pipe is stricter here than Apply, which treats a non-function operand as a constant: ["Apply", 3, 5] evaluates to 3 (this shorthand is relied on by operators such as Map).

A \square topic marker in the right-hand side names the position the piped value fills, so a stage can be a multi-argument call:

x^2 = 4 \rhd \operatorname{Solve}(\square, x)
$$$x^2 = 4 \rhd \operatorname{Solve}(\square, x)$$
["Solve", ["Equal", ["Power", "x", 2], 4], "x"]

(In Epsil, the topic marker is spelled _: xs |> Take(_, 10).)

The topic marker may be left out when it would fill the first slot: a call stage that is missing required arguments receives the piped value as an implicit first argument. A call that is already complete is never rewritten — the implicit argument only fills a hole.

["Pipe", ["Range", 1, 10], ["Take", 3]]
// Evaluates as ["Take", ["Range", 1, 10], 3] ➔ [1, 2, 3]

A unary function literal on the right-hand side of a pipe maps over a collection topic instead of being applied to the collection as a whole (an implicit Map); in Epsil, xs |> x |-> x^2 and xs |> _^2 are both Map(x ↦ x², xs).

["Pipe", ["List", 1, 2, 3], ["Function", ["Power", "x", 2], "x"]]
// Evaluates as ["Map", …, ["List", 1, 2, 3]] ➔ [1, 4, 9]

A stage that is a function symbol always receives the whole value — xs \rhd \operatorname{Sum} sums the collection, it does not map — as do a string topic (a scalar in a pipeline) and a lambda whose annotated parameter accepts the whole collection.

A bare function command such as \ln, \lb or \sqrt acts as a function reference (12 \rhd \ln is ln(12)), and the prefix form (\rhd f, with no left-hand side) denotes the anonymous unary function that applies f to its argument.

Operators whose unknown/variable argument is inferable — Solve, D, Series, and the polynomial operators such as Apart — may be piped into without naming the variable: x^2 = 4 \rhd \operatorname{Solve} solves for x.

EvaluateAt(function, point)

EvaluateAt(function, lower, upper)

The value of function at point, or, with two points, the difference function(upper) − function(lower), the bracket of the fundamental theorem of calculus.

\left.\left(x^2\right)\right|_{x=3}
$$$\left.\left(x^2\right)\right|_{x=3}$$
["EvaluateAt", ["Function", ["Power", "x", 2], "x"], 3]
// ➔ 9
\left.\left(\frac{x^3}{3}\right)\right|_{x=0}^{1}
$$$\left.\left(\frac{x^3}{3}\right)\right|_{x=0}^{1}$$
["EvaluateAt", ["Function", ["Divide", ["Power", "x", 3], 3], "x"], 0, 1]
// ➔ 1/3

The function is applied as Apply applies it: a function literal, a symbol whose value is a function, or an expression, which is read as a shorthand function literal of its free variables. A point that is a Tuple with one coordinate per parameter gives the value at that point of several variables: \left.\left(xy\right)\right|_{x=1, y=2} is ["EvaluateAt", ["Function", ["Multiply", "x", "y"], "x", "y"], ["Tuple", 1, 2]], which is 2. A point is never a partial application: with fewer points than the function requires, the expression stays as written.

When the bracket does not name the variable and the expression has several free variables, the point or the bounds are for x: \left.kx\right|_{2}^{5} is 3k. Write \left.\left(kt\right)\right|_{t=2}^{5} to name another variable.

The variable of a derivative, of a series or of an indefinite integral is free in its result, so subs() of that variable produces an EvaluateAt: ["D", ["Power", "x", 2], "x"] with x replaced by 3 is the derivative at 3, ["EvaluateAt", ["Function", ["D", ["Power", "x", 2], "x"], "x"], 3], which evaluates to 6. A value with no closed form stays an unevaluated EvaluateAt. The value of a series at a point keeps its BigO remainder, which is NaN under .N(): wrap the series in Normal to drop it.