Skip to main content

Core

The functions described in this section are part of the core of the Compute Engine.

Constants

The symbols below are inert constants. They are used as tags and have no value other than themselves.

SymbolDescription
AllAll the possible values apply
NoneNone of the possible values apply
NothingAn optional expression is not present. Used in sparse list to indicate skipped elements.
UndefinedThe result is not defined.
Note that for numbers, the equivalent is NaN (Not a Number)
\lbrack 2, ,3 \rbrack
$$$\lbrack 2, ,3 \rbrack$$
["List", 2, "Nothing", 3]

Declaring, Assigning and Assuming

A ["Declare"] expression is used to declare a symbol in the current scope.

Once a symbol has been declared, its value can be changed using an ["Assign"] expression.

An ["Assume"] expression is used to assert a predicate about an expression. It is used to provide additional information to the system, for example to indicate that a variable is positive.

Declare(symbol, type)

Declare(symbol, type, value)

Declare(symbol, attributes)

Declare a new symbol in the current scope, and optionally set its type and value.

If a value is provided, Declare evaluates to that value; otherwise it evaluates to Nothing. If the symbol already has a definition in the current scope, evaluating the expression throws an error (it does not return an error value).

This is equivalent to let in JavaScript or var in Python.

An optional trailing attributes dictionary provides additional properties of the definition — mirroring the ce.declare() API — using any of these keys:

  • type: the type of the symbol
  • value: the initial value of the symbol
  • constant: if True, the symbol is a constant: its value cannot be changed (a later ["Assign"] is rejected)
  • holdUntil: "never", "evaluate" or "N" — controls when the symbol's value is substituted during evaluation, as for built-in constants such as Pi

A positional type or value takes precedence over the same key in the dictionary.

// Declare the speed of light as an immutable constant
["Declare", "c", "integer", 299792458, ["Dictionary",
["KeyValuePair", "constant", "True"]]]

To change the value of an existing (non-constant) symbol, use an ["Assign"] expression.

Declare is not a pure function since it changes the state of the Compute Engine.

DeclareType(name, type)

DeclareType(name, type, attributes)

Declare a new type in the current scope — the MathJSON mirror of the ce.declareType() API. The name is a symbol (or string) naming the type; the type operand is a string holding a type expression.

By default the declared type is nominal: only a value whose type is the named type itself is compatible. An optional trailing attributes dictionary with the key alias set to True declares a structural alias instead: any value whose type matches the definition is compatible.

// A structural alias: any pair of numbers is a "point"
["DeclareType", "point", "'tuple<number, number>'", ["Dictionary",
["KeyValuePair", "alias", "True"]]]

The declaration also declares a value constructor — an operator of the same name, in the same scope, which is what makes a nominal type inhabitable:

["DeclareType", "point", "'tuple<x: number, y: number>'"]

["point", 1, 2]
// ➔ ["point", 1, 2], of type "point"

The constructor's signature comes from the definition: a tuple definition gives one argument per element (named, if the elements are named), and any other definition gives a single argument. A record definition is the exception: it declares no constructor, since building a record from positional arguments would depend on the order its fields happen to be written in. Arguments are validated against that signature, so a wrong arity or an argument of the wrong type produces the usual error value.

A nominal constructor is inert: the application is the value, and its type is the declared type. An alias constructor is a checked identity instead — ["pair", 1, 2] validates (1, 2) against the definition and evaluates to that plain tuple.

Because the declaration claims both the type name and the value name, it is atomic: if the current scope already has a value or operator of that name, nothing is registered and an error value is returned. (A name in an outer scope is shadowed, not conflicted.) The host API's mint option has no attributes equivalent: a declaration through this operator always declares a constructor.

Evaluates to Nothing. The declaration takes effect at canonicalization time, so later statements of the same ["Block"] can use the type in their own annotations. A DeclareType for a name that an earlier DeclareType statement declared replaces that definition — constructor included — so re-running a program on the same engine works; a name declared any other way (e.g. via ce.declareType()) reports an error value instead.

In Epsil, the type statement lowers to this operator. The bare form declares a nominal type (no attributes); the type alias form declares a structural alias (the alias -> True attributes dictionary):

type point = tuple<x: number, y: number> // nominal
type alias pair = tuple<number, number> // structural alias
let p = point(1, 2)
let a: pair = (1, 2)

DeclareType is not a pure function since it changes the state of the Compute Engine.

Assign(symbol, value)

Set the value of symbol to value.

The value operand is evaluated eagerly, when the assignment is evaluated: any symbol that has a value at that moment is substituted permanently. A symbol with no value (an unknown) remains symbolic in the stored value, and resolves to its current value whenever symbol is evaluated. See When Is the Value Captured? for details.

If symbol has not been declared in the current scope, consider parent scopes until a definition for the symbol is found.

If a definition is found, change the value of the symbol to value if the value is compatible with the type of the symbol: once set, the type of a symbol cannot be changed.

If there is no definition for the symbol, add a new definition in the current scope, and use the value to infer the type of the symbol.

This is equivalent to = in many programming languages.

Assign is not a pure function.

Assume(predicate)

The predicate is an expression that evaluates to True or False.

The symbols in the predicate expression may be free, i.e. they may not have been declared yet. Asserting an assumption does not declare the symbols in the predicate.

The predicate can take the form of:

  • an equality: ["Assume", ["Equal", "x", 3]]
  • an inequality: ["Assume", ["Greater", "x", 0]]
  • a membership expression: ["Assume", ["Element", "x", "Integers"]]

Assume evaluates to a string reporting the outcome:

OutcomeMeaning
"ok"the assumption was recorded
"tautology"the assumption is already implied by the existing ones
"contradiction"the assumption conflicts with the existing ones
"not-a-predicate"the argument is not a proposition that can be assumed
"internal-error"the assumption could not be processed
["Assume", ["Greater", "x", 0]]
// ➔ "ok"

["Assume", ["Greater", 1, 0]]
// ➔ "tautology"

["Assume", ["Less", 1, 0]]
// ➔ "contradiction"

Every outcome is reported as a value: an argument that is not a predicate does not raise an error.

["Assume", 42]
// ➔ "not-a-predicate"

Assume is not a pure function since it changes the state of the Compute Engine.

The ce.assume() method returns the same outcomes as a JavaScript string. See Assumptions for details.

HoldValues(body)

HoldValues(body, symbols)

Evaluate body with its assigned free symbols shielded: for the duration of the evaluation, each such symbol becomes a pure symbol — its declared type and any in-scope assumptions apply, but its assigned value does not. This is the value-blind counterpart of evaluating body directly, analogous to Mathematica's Block[{x}, …].

With a single argument, every assigned, non-constant free symbol of body is shielded. With a second symbols argument (a List, Set, Tuple, or a single symbol) only the listed symbols are shielded; every other symbol resolves normally.

Built-in constants (Pi, ExponentialE, …) are never shielded, in-scope assumptions survive the shield, and the global values are intact after the evaluation.

// With x := 5 and a := 3
["HoldValues", ["Together", ["Add", ["Divide", 1, "x"], ["Divide", "a", ["Power", "x", 2]]]]]
// ➔ ["Divide", ["Add", "a", "x"], ["Power", "x", 2]] (without the wrapper: 8/25)

["HoldValues", ["Add", ["Power", "x", 2], "a"], ["List", "a"]]
// ➔ ["Add", 25, "a"] (x resolves, a shielded)

Because the Simplify operator evaluates its argument before applying its rules, HoldValues is how you keep an assigned symbol symbolic through a Simplify on the operator surface — e.g. ["HoldValues", ["Simplify", ["Abs", "w"]]] with w := 5 is |w|, not 5.

Granular alternative. To shield a single symbol with a specific type rather than blanket-shielding, compose Block and Declare: declaring the symbol afresh in a local scope shadows the outer value for the block's duration.

// With w := 5
["Block", ["Declare", "w", "'real'"], ["Simplify", ["Abs", "w"]]]
// ➔ ["Abs", "w"]

Structural Operations

The following functions can be applied to non-canonical expressions. They do not depend on the canonical form, but reflect the structure of the expression.

About(symbol)

Evaluate to a dictionary expression containing information about a symbol such as its type, its attributes, its value, etc...

Tail(expression)

Evaluate to a sequence of the arguments of expression.

["Tail", ["Add", 2, 3]]
// ➔ ["Sequence", 2, 3]

Tail can be used to change the head of an expression, for example:

["Multiply", ["Tail", ["Add", 2, 3]]]
// ➔ ["Multiply", 2, 3]

Hold(expression)

Tag an expression that should be kept in an unevaluated form

Identity(expression)

Evaluate to its argument

In the mathematical sense, this is an operator (a function that takes a function as an argument and returns a function).

Inspecting an Expression

The following functions can be used to obtain information about an expression.

Type(expression)

Evaluate to the type of expression, as a string.

["Type", 2.4531]

// ➔ "finite_real"

IsSame(expression1, expression2)

Evaluate to True if the two expressions are structurally identical, otherwise evaluate to False.

["IsSame", ["Add", 2, 3], ["Add", 2, 3]]
// ➔ True

To compare two expressions for mathematical equality, use Equal.

To compare two expressions structurally, but ignoring the order of the arguments of commutative functions, use CanonicalForm.

See Comparing Expressions for other options to compare two expressions, such as the Equal function.

Same(expression1, expression2, ...) -> boolean

Evaluate to True if every adjacent pair of operands is syntactically identical, otherwise False. This is the === operator in Epsil, also written (U+2263). It is the operator counterpart of the expr.isSame() method.

Same is total: it always decides. It compares the canonical form of its operands as written — with no tolerance, and without numerically approximating an exact value.

["Same", ["Sqrt", 2], 1.4142135623730951]
// ➔ "False"

["Equal", ["Sqrt", 2], 1.4142135623730951]
// ➔ "True"

Same never dereferences the value of a symbol: two expressions that are written differently are not the same, even if they have the same value. With x assigned the value 5, ["Same", "x", 5] is False while ["Equal", "x", 5] is True.

Equal is the semantic, tolerant comparison: it may stay unevaluated when the answer is not known, since x = y is a condition. Same answers regardless:

["Same", "x", "y"]
// ➔ "False"

["Equal", "x", "y"]
// ➔ ["Equal", "x", "y"] (unevaluated)

Number leaves compare by exact value, not by notation:

["Same", 0.5, ["Divide", 1, 2]]
// ➔ "True"

Totality also means Same has no IEEE exemption for NaN, where Equal does. This holds inside a collection as well:

["Same", "NaN", "NaN"]
// ➔ "True"

["Equal", "NaN", "NaN"]
// ➔ "False"

["Same", ["List", "NaN"], ["List", "NaN"]]
// ➔ "True"

["Equal", ["List", "NaN"], ["List", "NaN"]]
// ➔ "False"

Same is not broadcast over collections: a list operand is compared as a whole, so ["Same", ["List", 1, 2], ["List", 1, 2]] is the scalar True, not a list of booleans.

With more than two operands, Same is a chain — ["Same", 1, 1, 1] is True — matching the Epsil spelling a === b === c.

Same vs IsSame. IsSame compares its operands exactly as written, while Same compares their canonical forms. So ["IsSame", ["Add", 1, 1], 2] is False, but ["Same", ["Add", 1, 1], 2] is True, since canonicalization folds 1 + 1 to 2.

IdenticallyEqual(expression1, expression2) -> boolean

Evaluate to True if the two expressions are identically equal, that is if they have the same value for every value of their free variables. This is the operator counterpart of the expr.isIdenticallyEqual() method.

(x+1)^2 \equiv x^2 + 2x + 1
$$$(x+1)^2 \equiv x^2 + 2x + 1$$
["IdenticallyEqual", ["Square", ["Add", "x", 1]],
["Add", ["Square", "x"], ["Multiply", 2, "x"], 1]]
// ➔ "True"

Unlike Equal, which compares the value of its operands, IdenticallyEqual proves an identity: it applies symbolic transformations (expansion and simplification) and evaluates both expressions at a number of pseudo-random sample points. A True answer that rests on sampling is a very strong indication rather than a formal proof — this is the only comparison operator that can answer from sampling.

Like Equal, it is three-valued: True, False, or — when the identity can neither be established nor refuted — unevaluated. Note that expressions that merely disagree at the sampled points are undetermined, not False:

["IdenticallyEqual", 1, 2]
// ➔ "False"

["IdenticallyEqual", ["Sin", "x"], ["Cos", "x"]]
// ➔ ["IdenticallyEqual", ["Sin", "x"], ["Cos", "x"]] (unevaluated)

The LaTeX notation is \equiv (or the character). A \equiv followed by \pmod{n} is a congruence and parses as Congruent instead.

Transforming an Expression

Evaluate(expression)

Apply a sequence of definitions to an expression in order to reduce, simplify and calculate its value. Overrides Hold and hold attributes of a function.

Evaluate only performs exact calculations. To perform numerical approximations, use N.

Read more about exact calculations and approximate calculations.

Expand(expression)

Apply the distributive law if the expression is a product or power of sums.

For example: a(b + c) = ab + ac

  • Expand the terms of the expression if it is a sum or negate.
  • If the expression is a fraction, expand the numerator.
["Expand", ["Power", ["Add", "x", 1], 2]]
// ➔ ["Add", 1, ["Multiply", 2, "x"], ["Power", "x", 2]]

ExpandAll(expression)

Expand an expression, recursively.

["ExpandAll", ["Power", ["Multiply", ["Add", "x", 1], 3], 2]]
// ➔ ["Add", 1, ["Multiply", 6, "x"], ["Multiply", 6, ["Power", "x", 2]], ["Power", "x", 3]]

Factor(expression)

Factor an expression.

["Factor", ["Add", ["Multiply", 2, "x"], ["Multiply", 2, "y"]]]
// ➔ ["Multiply", 2, ["Add", "x", "y"]]

Together(expression)

Combine the terms of a sum of fractions into a single fraction.

["Together", ["Add", ["Divide", 1, 2], ["Divide", 1, 3]]]
// ➔ ["Divide", 5, 6]

Simplify(expression)

The Simplify function applies a sequence of transformations to an expression in order to reduce, simplify and calculate its value.

Solve(equation)

Solve(equation, unknown)

Solve(equation, spec-1, spec-2, ...)

Return a list of the solutions of equation for the given unknowns.

To solve a system, pass a List of equations and a List of unknowns. Each solution is a Tuple whose values follow the order of the unknown list:

["Solve",
["List",
["Equal", ["Add", "x", "y"], 3],
["Equal", ["Subtract", "x", "y"], 1]],
["List", "x", "y"]]
// ➔ ["List", ["Tuple", 2, 1]]

Linear systems are solved exactly. An underdetermined system may return a parametric tuple, while a system the solver cannot decide remains unevaluated.

The equation is an Equal expression, or a bare expression that is solved as if it were equal to zero. Any boolean condition (an inequality, a congruence…) also works when a domain is given (see below).

When no unknown is given, it defaults to the single free variable of the equation, or to x when the equation has several free variables and one of them is x. This makes point-free forms such as x^2 = 4 \rhd \operatorname{Solve} work without naming the unknown.

Each spec describes one unknown, and is either:

  • a symbol — the unknown, solved over its declared type;
  • ["Element", _symbol_, _collection_] — the unknown restricted to a domain, such as a Range of integers or an Interval;
  • ["Element", _symbol_, _collection_, _condition_] — with an extra boolean filter, using the same indexing-set convention as Sum and Product.

In LaTeX, x \in 1..1000 parses to the Element form, so \operatorname{Solve}(x^2=4,\; x \in 1..1000) works without any special syntax.

["Solve", ["Equal", ["Power", "x", 2], 1], "x"]
// -> ["List", 1, -1]

["Solve",
["Equal", ["Subtract", ["Power", "x", 2], ["Multiply", 5, "x"]], -6],
["Element", "x", ["Range", 1, 1000]]
]
// -> ["List", 2, 3]

With a domain, the equation is solved symbolically and the solutions are filtered to the domain. Equations whose solutions form an infinite periodic family (such as trigonometric equations) return every member within the domain, not just the principal values. When no symbolic solution is found and the domain is finite and reasonably sized, Solve falls back to enumerating the domain; every candidate is confirmed exactly, and a search that would be too large returns the expression unevaluated rather than a partial answer.

With several unknowns, each carrying a domain, the result is a List of Tuples in unknown order:

["Solve",
["Equal", ["Add", ["Power", "x", 3], ["Power", "y", 3]], 1729],
["Element", "x", ["Range", 1, 12]],
["Element", "y", ["Range", 1, 12]]
]
// -> ["List", ["Tuple", 1, 12], ["Tuple", 9, 10], ["Tuple", 10, 9], ["Tuple", 12, 1]]

Integer (diophantine) equations are solved symbolically. When every unknown ranges over integers, linear equations in any number of unknowns, Pell-family equations $x^2 - Dy^2 = N$, and the Pythagorean equation $x^2 + y^2 = z^2$ are solved in closed form. This reaches answers enumeration cannot: solving $x^2 - 29y^2 = 1$ over $x, y \in 1..10^5$ returns [(9801, 1820)] even though the domain has \(10^10\) candidate pairs, and an unsolvable equation such as $6x + 9y = 4$ returns the empty list immediately.

When the unknowns are declared with an integer type and no domain is given, the result is the general parametric family, expressed with fresh integer parameters (t, t_1, …) that range over all integers:

// with n and m declared as integers:
["Solve", ["Equal", ["Add", ["Multiply", 3, "n"], ["Multiply", 4, "m"]], 7],
"n", "m"]
// -> ["List", ["Tuple", ["Add", ["Multiply", 4, "t"], -7],
// ["Add", ["Multiply", -3, "t"], 7]]]
// i.e. n = 4t − 7, m = −3t + 7 for all integers t

Pythagorean triples return the complete classical two-family parametrization: Solve(x²+y²=z², x, y, z) (with integer-typed unknowns) yields $\bigl(t(t_1^2-t_2^2),; 2t,t_1 t_2,; t(t_1^2+t_2^2)\bigr)$ and its leg-swapped counterpart — every integer solution, including all signs, is a member of one of the two families.

Assumptions on the unknown (such as assume(n > 0)) filter the solutions the same way, conjunctively with any explicit domain.

Solve is the operator form of the expr.solve() method, and uses the same solver for the two-argument symbolic case. The result is a List of the solutions, or an empty list when none are found.

CanonicalForm(expression)

CanonicalForm(expression, form-1, form-2, ...)

If expression is already canonical, this function has no effect.

If there are no form-n arguments, the expression is transformed to its canonical form.

If some form-n arguments are provided, they indicate one or more canonical transformations to apply to the expression. The following canonical forms are supported:

  • Order: If expression is a commutative function, sort the arguments according to the canonical order of the arguments of the function.
["CanonicalForm", ["Add", 3, 2, 1], "Order"]
// -> ["Add", 1, 2, 3]

This can be useful to compare two non-canonical expressions for equality, for example:

["IsSame",
["Add", 1, "x"],
["Add", "x", 1]
]
// -> False

["IsSame",
["CanonicalForm", ["Add", 1, "x"], "Order"],
["CanonicalForm", ["Add", "x", 1], "Order"]
]
// -> True
  • Flatten: Simplify associative expressions, remove any unnecessary delimiters indicating the order of operations, flattens any Sequence expressions.
["CanonicalForm", ["Add", 1, ["Add", 2, 3]], "Flatten"]
// -> ["Add", 1, 2, 3]

["CanonicalForm", ["Add", 1, ["Delimiter", ["Sequence", 2, 3]]], "Flatten"]
// -> ["Add", 1, 2, 3]

["CanonicalForm", ["Add", 1, ["Sequence", 2, 3]], "Flatten"]
// -> ["Add", 1, 2, 3]
  • Number: Transform some number forms, for example ["Add", 2, ["Multiply", 3, "ImaginaryI"]] to ["Complex", 2, 3], simplify and normalize numerator and denominator of rational numbers, etc...

  • InvisibleOperator: Remove any invisible operators that may be contained in the expression and replace them with Multiply or function application, depending on the context

["CanonicalForm", ["InvisibleOperator", "2", "x"], "InvisibleOperator"]
// -> ["Multiply", 2, "x"]
  • Multiply: If expression is a Multiply function, simplify it by combining the coefficients and the factors, transform product to a Power expression when possible.
["CanonicalForm", ["Multiply", 2, 3, "x"], "Multiply"]
// -> ["Multiply", 6, "x"]
  • Add: If expression is an Add function, remove any 0, transform sum into multiplication when possible. If expression is a Subtract transform it into an Add. If expression is a Negate transform it into a Multiply or negate number literals.

  • Power: Transform Exp, Square, Sqrt, Root function to a Power expression;

["CanonicalForm", ["Exp", "x"], "Power"]

```json example
["CanonicalForm", ["Power", 2, 3], "Power"]
// -> ["Power", 8]

To compare the input from a mathfield with an expected answer, you could use:

const correct = ce.parse(mf.value, {canonical: "Order"})
.isSame(ce.parse("1+x"))

Both 1+x and x+1 will return true, but 2-1+x will return false.

Note: The Divide form internally applies the Power form to its operands. If you need division canonicalization, it is not necessary to separately specify Power in your form list, though it does no harm.

The result of partial canonicalization is a structural expression. Calling .canonical on the result will perform full canonicalization. The form application order matters: forms are applied sequentially, and each form may benefit from transformations made by earlier forms.

Note: see also the options for the canonical option of ce.parse() and ce.expr() which can also be used to specify a custom canonical form:

const correct = ce.parse(mf.value, {canonical: "Order"})
.isSame(ce.parse("x+1"))

N(expression)

Evaluate to a numerical approximation of the expression.

["N", "Pi"]

// ➔ 3.141592653589793

N(expression, precision)

Evaluate to a numerical approximation with the given number of significant digits.

If precision is greater than the engine's current working precision (ce.precision), the working precision is raised to match — and kept raised, since display precision is a global setting. If precision is at or below the working precision, the result is rounded to that many significant digits without changing the working precision.

["N", "Pi", 20]
// ➔ 3.1415926535897932385

["N", ["Divide", 1, 3], 4]
// ➔ 0.3333

Core Functions

Error(error-code, context)

Tag an expression that could not be interpreted correctly. It may have a syntax error, a reference to an unknown symbol or some other problem.

The first argument, error-code is either a string, or an ["ErrorCode"] expression.

The context is an optional expression that provides additional information about the error.

An error is an ordinary value. When a strict operand of an expression evaluates to an error, the whole expression evaluates to that error rather than to a frozen tree. See Errors for the propagation rules.

The ErrorTrace Breadcrumb

An error that propagated out of the expression where it was raised carries a breadcrumb recording the operators it passed through. The breadcrumb is an ["ErrorTrace"] expression, and it is always the last operand of the ["Error"]:

["Add", ["Ln", "'a'"], 2]
// ➔ ["Error",
// ["ErrorCode", "'incompatible-type'", "'number'", "'string'"],
// ["ErrorTrace", ["ErrorFrame", "'Ln'", 1], ["ErrorFrame", "'Add'", 1]]]

Each frame is an ["ErrorFrame", operator, index] expression, where index is the 1-based position of the operand the error came from. Frames read innermost first — the failure site comes first, the outermost operator last.

An error that never propagated keeps its historical shape unchanged, with no ["ErrorTrace"] operand:

["Error", "'oops'"]
// ➔ ["Error", "'oops'"]

So the breadcrumb is identified by its head, never by its position: to read it, take the last operand of the ["Error"] and check that its operator is ErrorTrace. Code that reads operand 2 as the error context must skip an ["ErrorTrace"] found there — a traced error whose context slot is empty is ["Error", code, ["ErrorTrace", ...]].

The breadcrumb is data, not display: toString() and the LaTeX serializer render a traced error exactly like an untraced one.

IsError(expression) -> boolean

Evaluate to True if expression evaluates to an error, False otherwise.

IsError holds its operand — a strict position would propagate the error away before it could be inspected — and it is total: it always answers.

["IsError", ["Ln", "'a'"]]
// ➔ "True"

["IsError", ["Add", 1, 1]]
// ➔ "False"

["IsError", "x"]
// ➔ "False"

An expression that is not itself an error but embeds one also reports True.

There is one exception: an error held inside a collection value is not reported, because a collection containing an error is still a well-formed collection.

["IsError", ["List", 1, ["Ln", "'a'"]]]
// ➔ "False"

NaN is not an error — it is an ordinary IEEE numeric value:

["IsError", "NaN"]
// ➔ "False"

To inspect an error rather than merely detect it, use Type (which returns "error"), or destructure it with an ["Error", ...] case in a Match expression.

InverseFunction(symbol)

Evaluate to the inverse function of its argument for example Arcsin for Sin.

\sin^{-1}(x)
$$$\sin^{-1}(x)$$
[["InverseFunction", "Sin"], "x"]

In the mathematical sense, this is an operator (a function that takes a function as an argument and returns a function).

String(expression)

Evaluate to a string made from the concatenation of the arguments converted to strings

["String", "x", 2]

// ➔ "'x2'"

Symbol(expression)

Evaluate to a new symbol made of a concatenation of the arguments.

["Symbol", "x", 2]

// ➔ "x2"

The symbol is not declared, it remains a free variable. To declare the symbol use Declare.

["Declare", ["Symbol", "x", 2], "real"]

Typed(expression, type)

Ascribe a type to expression for the benefit of the type system.

The type is a string such as {"str": "integer"}, or a type-name symbol such as integer (which is normalized to a string).

Typed is an ascription, not a runtime check: it asserts the type without verifying the value, and it is transparent at evaluation — Typed(x, type) evaluates to the value of x.

["Typed", ["Add", 2, 3], "'integer'"]
// The expression has type `integer`, and evaluates to 5

The main use of Typed is to annotate the parameters and return type of a function literal.

Parsing and Serializing Latex

Parse(string)

If expr is a ["LatexString"] expression, evaluate to a MathJSON expression corresponding to the LaTeX string.

["Parse", ["LatexString", "'\frac{\pi}{2}'"]]

// ➔ ["Divide", "Pi", 2]

Latex(expression)

Evaluate to a LatexString which is the expression serialized to LaTeX

LatexString(string)

Tag a string as a LaTeX string

Superscripts and Subscripts

These functions are all inert functions, that is they evaluate to themselves.

FunctionDescription
Subminusx_-
Subplus x_+
Subscriptx_{n}See below for details on subscript handling.
Substar x_*
Superdagger x^\dagger
Superminus x^-
Superplus x^+
Superstar x^*When the argument is a complex number, indicate the conjugate.

Subscript Handling

When a symbol has a subscript, the Compute Engine uses the symbol's declared type to determine how to interpret the subscript.

Subscripts on Regular Symbols

For symbols that are not declared as collections, simple subscripts become part of the symbol name:

LaTeXResultNotes
A_1A_1Numeric subscript becomes part of symbol name
A_{n}A_nSingle-letter subscript becomes part of symbol name
A_{max}A_maxMulti-letter subscript becomes part of symbol name
x_{ij}x_ijCommon for matrix indices
T_{max}T_maxCommon for named subscripts

Complex subscripts (containing operators, commas, or parentheses) create Subscript expressions that can be used in arithmetic:

LaTeXResultNotes
a_{n+1}["Subscript", "a", ["Add", "n", 1]]Expression subscript
a_{n,m}["Subscript", "a", ["Sequence", "n", "m"]]Multi-index
A_{(n+1)}["Subscript", "A", ["Add", "n", 1]]Parentheses indicate expression

These Subscript expressions can be used in arithmetic operations:

ce.parse('a_{n+1} + 1'); // → ["Add", ["Subscript", "a", ...], 1]
ce.parse('2 * a_{n+1}'); // → ["Multiply", 2, ["Subscript", "a", ...]]

Subscripts on Collection-Typed Symbols

When a symbol is declared as a collection type (list, tuple, matrix, etc.), all subscripts are converted to At() indexing operations:

ce.declare('v', 'list<number>');
ce.parse('v_n'); // → ["At", "v", "n"]
ce.parse('v_{n+1}'); // → ["At", "v", ["Add", "n", 1]]
ce.parse('v_{i,j}'); // → ["At", "v", ["Tuple", "i", "j"]]

The type of the resulting At() expression is inferred from the collection's element type. For example, if v is declared as list<number>, then v_n has type number and can be used in arithmetic:

ce.declare('v', 'list<number>');
ce.parse('v_n + 1'); // → ["Add", ["At", "v", "n"], 1]
ce.parse('2 * v_{n+1}'); // → ["Multiply", 2, ["At", "v", ...]]

This behavior allows natural mathematical notation for sequences and arrays while maintaining type safety.

Custom Subscript Evaluation

For mathematical sequences like Fibonacci numbers (F_n) or indexed coefficients (a_n), you can define a custom subscriptEvaluate handler that evaluates subscripted expressions:

ce.declare('F', {
subscriptEvaluate: (subscript, { engine }) => {
const n = subscript.re;
if (!Number.isInteger(n) || n < 0) return undefined;
// Calculate Fibonacci...
return engine.number(fibValue);
},
});

ce.parse('F_{10}').evaluate(); // → 55
ce.parse('F_5').evaluate(); // → 5
ce.parse('F_n').evaluate(); // → stays as Subscript(F, n)

Both simple subscripts (F_5) and complex subscripts (F_{10}) are kept as Subscript expressions when the base symbol has a subscriptEvaluate handler. This allows the handler to be called during evaluation.

The handler should return undefined when the subscript is symbolic or outside the valid domain, causing the expression to remain unevaluated.

Subscripted expressions with subscriptEvaluate have type number and can be used in arithmetic operations:

ce.parse('F_{5} + F_{3}').evaluate(); // → 8 (5 + 3)

Styled Subscripts

To apply a specific style to a subscript (italic, bold, etc.), use the appropriate LaTeX command:

LaTeXResultNotes
A_{\mathit{max}}A_max_italicItalic subscript
A_{\mathbf{max}}A_max_boldBold subscript
A_{\mathrm{max}}A_maxUpright (roman) subscript

The style suffix (_italic, _bold, etc.) becomes part of the symbol name, allowing you to distinguish between differently styled versions of the same subscript if needed.

Bracket Notation for Indexing

Regardless of the symbol's type, you can use bracket notation to explicitly create an At indexing expression:

LaTeXResultNotes
v[n]["At", "v", "n"]Explicit indexing
A[i,j]["At", "A", "i", "j"]Multi-dimensional indexing

This is useful when you want to index into a symbol without declaring its type, or when you need to be explicit about the indexing operation.