Skip to main content

Core

The core library holds the operations that every program uses: the markers for absent values, declarations and assignments, the inspection and control of evaluation, comparison, errors, types as values, strings, and the conversion to and from LaTeX. This introduction gives the concepts you need before you read the entries.

Syntax and library names​

Many core definitions are the engine form of an Epsil construct. You write the construct, and the engine receives the definition. The entries list these definitions under their MathJSON name.

Epsil syntaxDefinition
let x = 3, const c = 1Declare
x = x + 1Assign
f(x) = x^2, function f(x) { … }DefineFunction
x => x^2Function
xs |> sortPipe
f(...t)Spread
f(rate: 0.05)NamedArgument
x is integerMatchesType
type point = tuple<x: number, y: number>DeclareType
type shape = circle(r: number) | square(s: number)DeclareSumType
protocol Area { … }DeclareProtocol
type string is CopyableDeclareConformance

A library name with a lowercase Epsil spelling (head for Head, simplify for Simplify, missing for Missing) is shown with that spelling. A name without one (Hold, HoldValues, Subtype, Latex, N) keeps its MathJSON spelling.

Absent values​

Four values mark that a value is absent. They do not behave the same way.

ValueMeaning
nothingNo value at all. It is removed from argument lists and collection literals.
missingA position exists, but its value is absent (R NA, Julia missing).
UndefinedThe result is not defined.
NaNA number that is not defined (Not a Number).

nothing disappears where it is written. missing keeps its position, and an arithmetic operation on it gives NaN:

[12, nothing, 34]
// ➔ [12, 34]
[nothing + 1, missing + 1]
// ➔ [1, NaN]

isMissing is true for missing, Undefined and NaN. Coalesce returns the first operand that is not absent:

Coalesce(missing, NaN, 3, 4)
// ➔ 3

Declaring, assigning and assuming​

let declares a name whose value can change, and const declares a name whose value cannot change. The = operator gives a new value to a name that let declared. The value is evaluated when the assignment is evaluated.

const c = 299792458
let t = 2
t = t + 1
c * t
// ➔ 899377374

Once a name has a type, a new value must be compatible with that type. A name that has no value is a free symbol: it stays symbolic in expressions.

assume records a fact about a symbol, for example that it is positive. It does not declare the symbol. It evaluates to a string that reports the outcome: "ok" when the fact was recorded, "tautology" when the known facts already imply it, "contradiction" when it conflicts with them, and "not-a-predicate" when the argument is not a condition.

[assume(x > 0), assume(x > -1), assume(x < 0), assume(42)]
// ➔ ["ok", "tautology", "contradiction", "not-a-predicate"]

HoldValues evaluates an expression as if some names had no value. The declared type and the assumptions of each name still apply. With one argument, every name that has a value is held. With a list as the second argument, only the names in the list are held:

let x = 5
let y = 2
(x + y, HoldValues(x + y), HoldValues(x + y, [y]))
// ➔ (7, x + y, y + 5)

The structure of an expression​

An expression has a head, the name of its operator, and a tail, its operands. head and tail read the expression as it is written, before the operands are evaluated:

head(x^2)
// ➔ "Power"
[tail(1 + x)]
// ➔ [1, x]

Hold keeps an expression in its written form: the expression is not evaluated until ReleaseHold removes the Hold.

Hold(1 + 2)
// ➔ Hold(1 + 2)
ReleaseHold(Hold(1 + 2))
// ➔ 3

A function declared with hold receives each argument as it is written, not its value. Its body can then inspect the expression:

let a = 3
hold f(e) = head(e)
f(a + 1)
// ➔ "Add"

Comparing expressions​

The == operator compares values. It can use a tolerance, and it approximates an exact value when that is necessary. The === operator compares the expressions as they are written (after canonicalization). It never uses a tolerance and never reads the value of a name.

(sqrt(2) == 1.4142135623730951, sqrt(2) === 1.4142135623730951)
// ➔ (True, False)
let x = 5
(x == 5, x === 5)
// ➔ (True, False)

=== always gives True or False. == can stay unevaluated when the answer is not known, for example x == y with two free symbols. NaN is not equal to itself with ==, but it is the same as itself with ===:

(NaN == NaN, NaN === NaN)
// ➔ (False, True)

Exact evaluation and approximation​

Evaluation is exact. A result that has no exact decimal form stays symbolic:

ln(2)
// ➔ ln(2)

N gives a numeric approximation. A second argument sets the number of significant digits:

N(ln(2))
// ➔ 0.693147180559945309417
N(pi, 20)
// ➔ 3.1415926535897932385

simplify changes an expression to a simpler form, and solve finds the values of an unknown that make an equation true:

simplify(sin(x)^2 + cos(x)^2)
// ➔ 1
solve(x^2 - 5x + 6 == 0, x)
// ➔ [3, 2]

Errors are values​

A problem at run time, such as an argument of the wrong type, does not stop the program. It gives an Error value. The error goes up through the expressions that contain it, and becomes their value. isError tests for an error value:

isError(ln("a"))
// ➔ True

To make an error value of your own, use RuntimeError. Its argument is a code string, or an ErrorCode("code", details…) expression when the error carries data. Do not write Error(…) for this: a written Error marks the program itself as wrong.

function reciprocal(x) {
if x == 0 { RuntimeError("zero-has-no-reciprocal") } else { 1 / x }
}
[reciprocal(4), reciprocal(0)]
// ➔ [1/4, Error("zero-has-no-reciprocal")]

NaN is not an error. It is a number.

Types as values​

type gives the static type of an expression as a type value. The type is as precise as the engine can make it: the type of 3 is the literal type 3, a subtype of integer.

type(3)
// ➔ TypeFrom("3")

typeFrom makes a type value from its text, and stringFrom gives the text of a type value. The is operator tests whether a value has a type, and Subtype tests whether one type is a subtype of another:

(3 is integer, 3 is string)
// ➔ (True, False)
Subtype("integer", "real")
// ➔ True

Strings​

A string is a sequence of characters. A character is what a reader sees as one character (a grapheme cluster), even when Unicode encodes it with several code points. length, characters and the other string operations count characters. unicodeScalars, utf8 and utf16 give the encoded integers.

characters("naïve")
// ➔ ["n", "a", "ï", "v", "e"]

A string literal can include the value of an expression with \(…):

let n = 7
"n = \(n)"
// ➔ "n = 7"

LaTeX​

Latex converts an expression to a LaTeX string, and parse converts a LaTeX string to an expression. In an extended string literal (#"…"#), a backslash is an ordinary character, so LaTeX commands need no escapes:

Latex(x^2 / 2)
// ➔ "\frac{x^2}{2}"
parse(#"\frac{x}{2}"#)
// ➔ 1/2 * x

Each definition is listed under its Epsil spelling (the MathJSON name when it has none), with its signature in the engine's type syntax. The Standard Library page is the one-page index of every category.

Definitions​

about​

MathJSON About · (any) -> dictionary<any>

Return information about an expression as a dictionary: its kind (symbol, constant, function, number, string, expression), its static type and, when applicable, its name, value, signature, clause listing, attributes (the algebraic flags and lazy), description, examples, keywords, wikidata and url.

about(pi)
// ➔ {"name" -> "Pi", "kind" -> "constant", "type" -> "real<3.141592653589793..3.141592653589794>", "description" -> "The constant π ≈ 3.14159, the ratio of a circle's circumference to its diameter.", "examples" -> ["N(Pi)","Cos(Pi)"], "wikidata" -> "Q167"}

angle​

MathJSON Angle · (any+) -> number

Angle mark / measure (\angle ABC, \varangle XYZ, ∠ABC) — opaque typed head; not evaluated.

angle(A, B, C)
// ➔ Angle(A, B, C)

Annotated​

(expression, dictionary<any>) -> expression

Attach metadata or style annotations to an expression.

Annotated(x^2, {"color" -> "blue"})
// ➔ x^2

apply​

MathJSON Apply · (name: any, arguments: any*) -> unknown

Apply a function to a list of arguments

apply(sqrt, 16)
// ➔ 4

applyWhole​

MathJSON ApplyWhole · (name: any, arguments: any*) -> unknown

Apply a function to arguments, each bound whole (engine-internal).

arc​

MathJSON Arc · (any+) -> number

Arc / wide-hat accent measure (\widehat{ABC}) — opaque typed head; not evaluated.

arc(A, B, C)
// ➔ Arc(A, B, C)

Assign​

(expression | symbol, any) scope -> any

Assign a value to a symbol or define a sequence. The RHS is evaluated immediately and ce.assign(name, val) mutates the binding in the current scope chain. When used inside a Block, the assignment is visible to subsequent statements in the block (sequential semantics).

let x = 3
x = x + 1
x

assume​

MathJSON Assume · (any) scope -> string

Record an assumption about a symbol. Evaluates to the outcome as a string: "ok", "tautology", "contradiction", "not-a-predicate" or "internal-error".

assume(x > 0)

baseForm​

MathJSON BaseForm · (T, (number | string)?) -> T where T: number

BaseForm(expr, base=10)

Block​

(unknown*) -> unknown

Evaluate a sequence of expressions in a local scope, sequentially. Each operand is evaluated in order; later operands observe side effects (Assign, Declare) of earlier operands. The block's value is the value of the last expression. Short-circuiting heads (Return, Break, Continue) terminate the sequence early.

IMPORTANT — consumers translating simultaneous action tuples (e.g. Desmos (a → 1, b → a + 1) where b reads the pre-action a) must rewrite to a snapshot-then-commit Block: bind each RHS to a fresh temp first, then assign the temps to the LHS symbols. See doc/84-reference-control-structures.md for the canonical recipe.

BuiltinFunction​

(string | symbol) -> symbol

Return a built-in function symbol by name.

BuiltinFunction("Sqrt")(16)
// ➔ 4

canonicalForm​

MathJSON CanonicalForm · (any, symbol*) -> any

Return the canonical form of an expression

Can be used to sort arguments of an expression.

Sorting arguments of commutative functions is a weak form of canonicalization that can be useful in some cases, for example to accept "x+1" and "1+x" while rejecting "x+1" and "2x-x+1"

canonicalForm(Hold(1 + x), "Order")
// ➔ Hold(x + 1)

caseFold​

MathJSON CaseFold · (string) -> string

CaseFold(s): a case-folded form of s, for case-insensitive comparison — CaseFold(a) == CaseFold(b) tests equality ignoring case. An approximation of Unicode full case folding.

caseFold("Straße")
// ➔ "strasse"
caseFold("Hello") == caseFold("HELLO")
// ➔ "True"

characterFrom​

MathJSON CharacterFrom · (string) -> character

CharacterFrom(s): the character s denotes. s must be exactly one user-perceived character (one grapheme cluster) after NFC normalization; an empty or multi-character string is an error.

characterFrom("é")
// ➔ "é"

characters​

MathJSON Characters · (string) -> list<character>

Characters(s): split a string into a list of user-perceived characters (grapheme clusters). Synonym: GraphemeClusters. For stable integer decompositions see UnicodeScalars, Utf8 and Utf16. A non-string argument leaves the expression unevaluated.

characters("héllo")
// ➔ ["h","é","l","l","o"]

Coalesce​

(any+) -> unknown

Return the first operand that is not ABSENT (Missing, Undefined or NaN), evaluated left-to-right. If every operand is absent, the last operand’s value is returned verbatim (still absent). Indeterminate is a value and is not absent.

Coalesce(missing, NaN, 3, 4)
// ➔ 3

Colon​

(any, any) -> expression

Type annotation (a : b) — opaque typed head.

conforms​

MathJSON Conforms · (subject: any, protocols: string+) -> boolean

True iff the subject conforms to EVERY named protocol. A type VALUE subject asks whether that type conforms (the branch is unambiguous because the type primitive itself declares no conformances); any other subject is evaluated once and its precise type is asked. This is the lowering of the Epsil x is Hashable & Comparable test. A valueless or unresolved subject stays symbolic; an unknown protocol name is an error; an Error-valued subject answers False (the error type declares no conformances). Conformance is monotone but late-bound: the answer reflects the registry at the moment of evaluation.

protocol Copyable {}
type string is Copyable
conforms("abc", "Copyable")
// ➔ "True"

Declare​

(symbol, type: (string | symbol)?, value: any?, attributes: dictionary<any>?) scope -> any

Declare a symbol in the current scope, optionally assigning a type and an initial value. An optional trailing attributes dictionary (with keys type, value, constant and holdUntil) can further describe the definition, e.g. to declare a constant. With a value, evaluates to that value; otherwise evaluates to Nothing.

let x: integer = 5
x + 1

DeclareConformance​

(target: string | symbol, protocols: any, whereClauseOrImplementation: any?, implementation: dictionary<any>?) scope -> nothing

Declare that a type CONFORMS to one or more protocols — the lowering of the Epsil type string is Hashable & Comparable statement. The target rides as a type-expression string and must be named and ground (not a union, an anonymous structural type or a type alias name); the protocols ride as a List of names. An optional trailing dictionary carries the implementation block, member name -> function literal (property handlers under the mangled keys __get__x / __set__x); it may only accompany a SINGLE protocol. A CONDITIONAL conformance carries, ahead of that block, the source text of its trailing where clause as a string: the target is then a head pattern naming the variables the clause binds (list<T> with "where T is Comparable"). Conformance is monotone — it can be added but never removed — and a re-declaration is a no-op. Evaluates to Nothing.

protocol Copyable {}
type string is Copyable
"abc" is Copyable
// ➔ "True"

DeclareProtocol​

(string | symbol, members: dictionary<any>?) scope -> nothing

Declare a PROTOCOL: a set of function and property requirements a type may declare itself to satisfy. Protocols are engine-global (not lexically scoped) and are NOT types, so this is only valid at the top level of a program. The name is a symbol (or a string); the optional members ride as a dictionary of member -> ["Pair", "function"|"readonly"|"readwrite", signature], with the signature as a type-expression string. A function member's first parameter must be typed Self, the substitution token standing for the conforming type. A protocol with no members is a SEMANTIC protocol (a marker). Evaluates to Nothing.

protocol Area { function area(self: Self) -> number }
type square = tuple<side: number> is Area {
function area(self: square) -> number { self.side^2 }
}
area(square(3))

DeclareSumType​

(string | symbol, any*) scope -> nothing

Declare a SUM TYPE: N nominal variants plus the transparent union that names them, in one statement — the lowering of the Epsil sugar type node = lit(num: number) | plus(op1: node, op2: node). The name is a symbol (or a string); each variant is a ["Tuple", name, payload] pair whose payload is a type string ("nothing" for a nullary variant). An optional attributes dictionary at operand 1 — ahead of the variants — carries typeParams -> "T" for a generic sum, whose parameters are distributed to each variant by usage. The sum name is forward-registered before the variants are declared, so a payload may name it bare. A variant name that already names a type, is reserved, or is a builtin is rejected and NOTHING is declared. Types are engine-global, so this is only valid at the top level of a program. Evaluates to Nothing.

type shape = circle(r: number) | square(s: number)
match square(3) {
circle(r) => pi * r^2
square(s) => s^2
}

DeclareType​

(string | symbol, type: string | symbol | type, attributes: dictionary<any>?) scope -> nothing

Declare a type. Types are engine-global (not lexically scoped), so this is only valid at the top level of a program — inside a block or function body it is an error. The name is a symbol (or a string) and the type a string holding a type expression, e.g. "tuple<x: integer, y: integer>". The type is nominal by default; an optional trailing attributes dictionary with alias -> True makes it a structural alias instead, and an additional typeParams -> "T, U: number" entry makes it a GENERIC alias whose uses must be applied (Pair<integer>). The declaration also mints a value constructor of the same name — ["point", 1, 2], an inert tagged value for a nominal type, a checked identity for an alias — except for a record body, which mints none. Evaluates to Nothing.

type point = tuple<x: number, y: number>
point(1, 2)

DefineFunction​

(symbol, function, dictionary<any>?) scope -> nothing

Define one clause of a (possibly multi-clause) function: DefineFunction(f, Function(body, params…)). Unlike Assign — which replaces the binding wholesale — DefineFunction ACCUMULATES: a clause with the same parameter domain replaces the earlier clause in place, any other clause is appended, and calls dispatch to the most specific clause admitting the arguments.

fact(0) = 1
fact(n) = n * fact(n - 1)
fact(5)

Delimiter​

(any, string?) -> any

Group expressions with explicit delimiters.

Delimiter(1 + 2)
// ➔ 3

digitsFrom​

MathJSON DigitsFrom · (string, (integer | string)?) -> integer

Return an integer representation of the string s in base base.

digitsFrom("ff", 16)
// ➔ 255
digitsFrom("1010", 2)
// ➔ 10

error​

MathJSON Error · (expression<ErrorCode> | string, expression?) -> nothing

Represent an error expression.

[1, RuntimeError("zero")]
// ➔ [1,Error("zero")]

ErrorCode​

(string, any*) -> error

Structured error code with optional arguments.

[1, RuntimeError(ErrorCode("out-of-range", 5))]
// ➔ [1,Error(ErrorCode("out-of-range", 5))]

evaluate​

MathJSON Evaluate · (any) -> unknown

Evaluate an expression.

evaluate(x + x)
// ➔ 2x

evaluateAt​

MathJSON EvaluateAt · (function, lower: expression, upper: expression) -> unknown

Evaluate a function at one point or between two bounds.

evaluateAt(x => x^2, 1, 3)
// ➔ 8

findRoot​

MathJSON FindRoot · (any, any) -> dictionary

FindRoot(equations, params): numerically find parameter values that

zero the residuals. equations is an equation (lhs == rhs), a bare

residual expression (read as = 0), or a list of either. params is

a list of specs (a bare symbol, (a, a0), or (a, a0, lo, hi) with

box constraints), matching FindFit. Returns a record

{parameters, converged, residualNorm, iterations}.

findRoot(x^2 - 2, [(x, 1)])
// ➔ {"parameters" -> {"x" -> 1.4142135624638652}, "converged" -> "True", "residualNorm" -> 2.567368539985182e-10, "iterations" -> 4}

Function​

(expression, (function | symbol)*) -> function

A function literal

(x => x^2 + 1)(3)
// ➔ 10

geometricVector​

MathJSON GeometricVector · (any, any) -> expression

Geometric vector (directed segment between two points) — opaque typed head. Distinct from the column-vector Vector operator.

geometricVector(A, B)
// ➔ GeometricVector(A, B)

graphemeClusters​

MathJSON GraphemeClusters · (string) -> list<character>

A collection of grapheme clusters from a string. Synonym of Characters.

graphemeClusters("héllo")
// ➔ ["h","é","l","l","o"]

MathJSON Head · (any) -> symbol

Return the head of an expression, the name of the operator

head(x^2)
// ➔ "Power"

Hold​

(any) -> unknown

Hold an expression, preventing it from being canonicalized or evaluated until ReleaseHold is applied to it

Hold(1 + 2)
// ➔ Hold(1 + 2)

HoldValues​

(any, any?) -> expression

HoldValues(body): evaluate body with its assigned free symbols

shielded — each such symbol becomes a pure symbol (its declared type

and in-scope assumptions apply, its assigned value does NOT) for the

duration. The value-blind counterpart of evaluating body directly;

analogous to Mathematica's Block[{x}, …].

HoldValues(body, [x, y]): shield only the listed symbols (a List,

Set, Tuple, or a single symbol); every other symbol resolves normally.

Constants (Pi, ExponentialE, …) are never shielded, assumptions

survive the shield, and the global values are intact afterwards.

let x = 5
let y = 2
(x + y, HoldValues(x + y), HoldValues(x + y, [y]))

HorizontalSpacing​

(number) -> nothing

Horizontal spacing annotation.

identity​

MathJSON Identity · (T) -> T where T

Return the argument unchanged

identity(x + 1)
// ➔ x + 1

IndexedSequence​

(any, symbol, any, any?) -> expression

Indexed sequence \{a_n\}_{n=1}^{\infty} — inert head IndexedSequence(term, index, lower, upper?); not evaluated.

IndexedSequence(1/n, n, 1, oo)
// ➔ {1 / n : n = 1..+oo}

input​

MathJSON Input · (prompt: string?) console -> nothing | string

Read one line of text from the host: the terminal in a command-line host, the prompt() dialog in a browser. The optional operand is a prompt string, displayed before reading. Evaluates to the line read, without the trailing newline; to Nothing at end-of-input (or a canceled dialog). On a host with no interactive input, stays unevaluated. When the host denies console access, evaluates to a capability-denied error.

integerString​

MathJSON IntegerString · (integer, integer?) -> string

IntegerString(n, base=10) return a string representation of the integer n in base base.

integerString(255, 16)
// ➔ "ff"
integerString(10, 2)
// ➔ "1010"

InvisibleOperator​

function

Implicit operator used for juxtapositions such as function application or multiplication.

InvisibleOperator(2, x)
// ➔ 2x

isError​

MathJSON IsError · (any) -> boolean

True if the expression is an Error value, or a frozen expression embedding one ("a" + 1). False otherwise. Total.

isError(ln("a"))
// ➔ "True"
isError(1 + 1)
// ➔ "False"

isMissing​

MathJSON IsMissing · (any) -> boolean

True if the value is ABSENT — the Missing or Undefined symbol, or a NaN number (regardless of provenance). R’s is.na (TRUE for both NA and NaN). There is no NaN-specific test operator (R’s is.nan). Indeterminate, the exact answer to an indeterminate form such as 0/0, is a value and is not absent.

[isMissing(missing), isMissing(NaN), isMissing(0)]
// ➔ ["True","True","False"]

Latex​

(any+) -> string

Serialize an expression to LaTeX

Latex(sqrt(x) / 2)
// ➔ "\frac{\sqrt{x}}{2}"

LatexString​

(string) -> string

Value preserving type conversion/tag indicating the string is a LaTeX string

parse(LatexString(#"\frac{1}{2}"#))
// ➔ 1/2

MatchesType​

(subject: any, type: string | type) -> boolean

True iff the first operand, EVALUATED, is a value of the given type — the engine form of the Epsil x is T test and of match type patterns, which both lower here. The subject is never unwrapped: a type VALUE is a value like any other, so MatchesType(TypeFrom("integer"), "number") is False while MatchesType(TypeFrom("integer"), "type") is True; the type-to-type question is Subtype. A settled subject is decided both ways; a valueless or unresolved subject answers from its static type when that decides it, and stays symbolic otherwise.

MatchesType([1, 2], "list<integer>")
// ➔ "True"

missing​

MathJSON Missing · variable missing

A value that is absent but whose position is preserved (Julia missing, R NA); the sole member of the missing type.

missing + 1
// ➔ NaN

N​

(any, (integer | list<number>)?) -> unknown

N(expr): numerically evaluate an expression

N(expr, precision): evaluate to precision significant digits

N(expr, [precision, accuracy]): evaluate with a precision goal and an accuracy goal

N(pi)
// ➔ 3.14159265358979323846
N(1/3, 4)
// ➔ 0.3333
N(exp(100), [positiveInfinity, 20])
// ➔ 2.688117141816135448412625551580013587361111877374192241519160862e+43

NamedArgument​

(string, any) -> nothing

NamedArgument(name, value): one named argument of a call (Epsil

surface syntax: f(rate: 0.05)).

A parse-level carrier, like Spread, but one that never survives:

the enclosing call consumes it at canonicalization, permuting the

written arguments into the order its callee declares.

Reaching this definition therefore means the carrier was NOT

consumed — the callee supplied no parameter names to match — which

is the argument-names-unavailable error.

((x, y) => x - y)(y: 2, x: 10)
// ➔ 8

nothing​

MathJSON Nothing · variable nothing

The absence of a value; the sole member of the unit type.

[1, nothing, 2]
// ➔ [1,2]

numberFrom​

MathJSON NumberFrom · (string, base: (integer | string)?) -> number

NumberFrom(s): the number the string s denotes — optional surrounding whitespace, an optional sign, then ASCII digits with an optional "." fraction and an optional e/E exponent, or one of "oo", "+oo", "-oo", "NaN", "Indeterminate". The integer part may be omitted before a fraction (".5" is 0.5); a trailing "." with no fraction digits ("5.") is not accepted. Any other text, including "", is an error value (never NaN).

NumberFrom(s, base): the integer s denotes in base (2 to 36); only integer numerals are accepted.

numberFrom("3.25")
// ➔ 3.25
numberFrom("ff", 16)
// ➔ 255

numericApproximation​

MathJSON NumericApproximation · (any) -> unknown

Numerically evaluate an expression, as the .N() method does (engine-internal).

Object​

(any, string?) -> unknown

Provenance head for the snapshot of a mutable object: ["Object", <record>, "'TypeName'"]. The record holds the object's stored fields at the moment it was serialized; the second operand names the nominal type the object had. Not a constructor and not an ascription — it wraps data, it does not make an object.

OverParen​

(any+) -> expression

Over-paren accent (\overparen{BC}) — opaque typed head; not evaluated.

OverParen(B, C)
// ➔ OverParen(B, C)

padEnd​

MathJSON PadEnd · (string, n: integer, pad: string?) -> string

PadEnd(s, n, pad=" "): s padded at the END to n characters by repeating pad (its final copy truncated on a character boundary). Returned unchanged when s already has n or more characters. n must be a non-negative integer; an empty pad is an error; a non-string pad leaves the expression unevaluated.

padEnd("abc", 6, ".")
// ➔ "abc..."

padStart​

MathJSON PadStart · (string, n: integer, pad: string?) -> string

PadStart(s, n, pad=" "): s padded at the START to n characters by repeating pad (its final copy truncated on a character boundary). Returned unchanged when s already has n or more characters. n must be a non-negative integer; an empty pad is an error; a non-string pad leaves the expression unevaluated.

padStart("42", 5, "0")
// ➔ "00042"

parallel​

MathJSON Parallel · (any, any) -> expression

Parallelism relation (AB \parallel CD) — opaque typed head; not evaluated.

parallel(l, m)
// ➔ Parallel(l, m)

parse​

MathJSON Parse · (string) -> any

Parse a LaTeX string and evaluate to a corresponding expression

parse(#"\frac{\pi}{2}"#)
// ➔ 1/2 * pi

perpendicular​

MathJSON Perpendicular · (any, any) -> expression

Perpendicularity relation (AB \perp CD) — opaque typed head; not evaluated.

perpendicular(l, m)
// ➔ Perpendicular(l, m)

Pipe​

(value, function) -> unknown

Apply a function to a value: Pipe(x, f) evaluates to f(x).

Pipe([3, 1, 2], sort)
// ➔ [1,2,3]
16 |> sqrt
// ➔ 4

polygon​

MathJSON Polygon · (any+) -> expression

Polygon primitive — opaque typed head.

polygon(A, B, C, D)
// ➔ Polygon(A, B, C, D)

prime​

MathJSON Prime · (T, integer?) -> T where T

Derivative or prime notation (f', f^{(n)}) — opaque typed head until a derivative library handler runs.

prime(f)
// ➔ Prime(f)

print​

MathJSON Print · (any*) console -> nothing

Print the operands to the host console, separated by spaces and followed by a newline. String operands print their content (without quotes); other expressions print their text form. Evaluates to Nothing. On a host without a console, prints nothing. When the host denies console access, evaluates to a capability-denied error.

print("Hello", 42)

ProtocolMember​

(protocol: string, member: string, arguments: any*) -> unknown

Invoke a protocol member on a value — the lowering of a QUALIFIED protocol call (Comparable.compare(x, y) in Epsil, whose parse, a MemberCall on the protocol name, canonicalizes to Apply(Field(Comparable, "compare"), x, y)). The first two operands name the protocol and the member; the rest are the call arguments. Dispatch is dynamic and restricted to the named protocol: the most specific conformance implementation for the runtime type of the first argument is invoked. Several equally specific implementations are protocol-call-ambiguous; none is protocol-implementation-missing; an argument whose type cannot decide the question leaves the call symbolic.

protocol Negatable { function negated(self: Self) -> Self }
type number is Negatable { function negated(self) -> number { -self } }
Negatable.negated(5)
// ➔ -5

ProtocolProperty​

(protocol: string, property: string, receiver: any, value: any?) -> unknown

Read (or write) a protocol PROPERTY through a NAMED protocol — the lowering of the qualified field form person.(Nameable.name) (protocols design P6, amending the D16 field grammar). The first two operands name the protocol and the property; the third is the receiver. A fourth operand makes it a property STORE — the qualified write person.(Nameable.name) = v — which invokes the set accessor against the receiver, discards what it returns, and evaluates to the value assigned; a receiver that is not an object is immutable-value-assignment. Dispatch is dynamic and restricted to the named protocol: the most specific conformance implementation for the runtime type of the receiver is invoked.

protocol Signed { readonly sign: string }
type number is Signed {
get sign(self) -> string { if (self < 0) { "-" } else { "+" } }
}
let x = -12
x.(Signed.sign)

quadrilateral​

MathJSON Quadrilateral · (any+) -> expression

Quadrilateral mark (\square ABCD) — opaque typed head; not evaluated.

quadrilateral(A, B, C, D)
// ➔ Quadrilateral(A, B, C, D)

random​

MathJSON Random · ((collection<any> | set<real>)?) random -> any

Random(): non-deterministic real in [0, 1)

Random(Interval(a, b)): a real in [a, b) (endpoint markers ignored)

Random(Range(...)): an element of the range

Random(xs): an element of the finite collection xs

random()
random(1..6)

randomChoice​

MathJSON RandomChoice · ((T, number) random -> T where T: string) & ((collection<any> | set<real>, number) random -> list<any>)

RandomChoice(domain, k): a list of k independent draws from domain, with replacement. k may exceed the size of the domain — that is what replacement means. Choosing from a string yields a string.

randomChoice(["a", "b", "c"], 5)

randomExpression​

MathJSON RandomExpression · () entropy -> expression

Generate a random expression.

randomExpression()

ReleaseHold​

(any) -> unknown

Release an expression held by Hold

ReleaseHold(Hold(1 + 2))
// ➔ 3

replaceAll​

MathJSON ReplaceAll · (any, any+) -> any

ReplaceAll(expr, rules): apply one or more replacement rules to expr,

then evaluate the result (Mathematica expr /. rules).

A rule is Rule(lhs, rhs), or lhs -> rhs in LaTeX (parsed as To;

in Epsil -> builds a dictionary entry, which is not a rule). Several

rules may be given as extra arguments or as a List/Set of rules;

they are applied simultaneously in a single pass.

replaceAll(x^2 + x, Rule(x, 3))
// ➔ 12

Rule​

(match: expression, replace: expression, predicate: function?) -> expression

Pattern replacement rule.

replaceAll(x + y, Rule(x, 2))
// ➔ y + 2

RuntimeError​

(expression<ErrorCode> | string) -> never

Construct an error value when evaluated: the runtime counterpart of a written Error(…), which is a static diagnostic node. Evaluates to Error(code).

isError(RuntimeError("oops"))
// ➔ "True"

segment​

MathJSON Segment · (any+) -> expression

Segment primitive — opaque typed head.

segment(A, B)
// ➔ Segment(A, B)

Sequence​

function

Ordered sequence of expressions.

[0, Sequence(1, 2), 3]
// ➔ [0,1,2,3]

Signature​

(symbol) -> nothing | string

Return the signature string of an operator.

Signature(stringRepeat)
// ➔ "(string, n: integer) -> string"

simplify​

MathJSON Simplify · (any, any?) -> expression

Simplify(expr): simplify an expression.

Simplify(expr, assumptions): simplify under one or more boolean

assumptions (e.g. x > 0), or a List/And of them. The assumptions

hold only for the duration of the simplification.

simplify(sin(x)^2 + cos(x)^2)
// ➔ 1
simplify(sqrt(x^2), x > 0)
// ➔ x

solve​

MathJSON Solve · (any, any*) -> list

Solve(equation, unknown): the list of solutions of an equation for the

unknown. The equation may be an Equal expression or a bare expression

(read as = 0), e.g. Solve(x^2 - 1 == 0, x) or Solve(x^2 - 1, x).

The unknown may be omitted: it defaults to the equation's single free

variable, or to x when there are several and one of them is x.

Solve([eq1, eq2, …], [x, y, …]): solve a system of equations; each

solution is a tuple of values in the order of the variable list, e.g.

Solve([x + y == 3, x - y == 1], [x, y]) → [(2, 1)].

solve(x^2 - 1 == 0, x)
// ➔ [1,-1]
solve([x + y == 3, x - y == 1], [x, y])
// ➔ [(2, 1)]

sphere​

MathJSON Sphere · (any+) -> expression

Sphere primitive — opaque typed head.

sphere(O, r)
// ➔ Sphere(O, r)

Spread​

(any) -> unknown

Spread(t): splice the elements of the tuple t into the enclosing

argument list (Epsil surface syntax: f(...t)).

A literal tuple splices at canonicalization; a symbolic argument is

spliced by the enclosing call at evaluation (step 0 of the evaluate

path), which re-validates the resulting arity.

max(...(4, 9, 2))
// ➔ 9

String​

(any*) -> string

A string created by joining its arguments. The arguments are converted to their default string representation.

String("x", 2)
// ➔ "x2"

stringCompare​

MathJSON StringCompare · (string, string) -> integer

StringCompare(a, b): -1 when a sorts before b, 0 when they are equal, 1 when a sorts after b. The order compares Unicode scalar sequences code point by code point (NOT UTF-16 code units, which would sort astral characters below U+E000..U+FFFF).

stringCompare("apple", "banana")
// ➔ -1

stringFrom​

MathJSON StringFrom · (any, format: string?) -> string

StringFrom(value, format?): create a string from value. With no format, a number or a list of numbers is read as Unicode scalar values (StringFrom(65) is "A"), and any other value is printed (StringFrom(True) is "True"). The formats are "default" (print the value), "unicode-scalars", "utf-8" and "utf-16".

stringFrom(65)
// ➔ "A"
stringFrom([72, 105])
// ➔ "Hi"

stringJoin​

MathJSON StringJoin · (collection<character | string>, separator: string?) -> string

StringJoin(xs): join the elements of the finite collection xs (strings or characters) into a string.

StringJoin(xs, sep): the same, with sep between consecutive elements. The inverse of StringSplit. An empty collection joins to "", a one-element collection to that element. A non-text element, or a non-finite collection, leaves the expression unevaluated. For variadic concatenation use Join(a, b, …) or string interpolation.

stringJoin(["a", "b", "c"], "-")
// ➔ "a-b-c"

stringRepeat​

MathJSON StringRepeat · (string, n: integer) -> string

StringRepeat(s, n): n copies of the string s, concatenated. StringRepeat(s, 0) is "". A negative or non-integer n is an error.

stringRepeat("ab", 3)
// ➔ "ababab"

stringReplace​

MathJSON StringReplace · ((string, string, string, count: integer?) -> string) & ((string, regexp, string, count: integer?) -> string) & ((string, regexp, function, count: integer?) -> string)

StringReplace(s, target, replacement): replace every non-overlapping occurrence of target in s, scanning left to right over whole characters.

StringReplace(s, target, replacement, count): replace at most count occurrences, from the left. An empty target is an error (the "insert at every boundary" behavior is deliberately not inherited); an empty replacement means deletion. count must be a positive integer.

StringReplace(s, pattern, replacement, count?): target may be a regular expression, matched with the host dialect. $1-style templates are NOT expanded in replacement.

StringReplace(s, pattern, f, count?): replacement may be a function, called with the same match record StringMatch returns, so each replacement can be computed from its captures.

stringReplace("banana", "a", "o")
// ➔ "bonono"
stringReplace("banana", "a", "o", 1)
// ➔ "bonana"

stringSplit​

MathJSON StringSplit · ((string, string?) -> list<string>) & ((string, regexp) -> list<string>)

StringSplit(s): split a string on runs of whitespace (the Unicode White_Space code points), dropping empty parts.

StringSplit(s, sep): split a string on the separator string sep (empty parts are kept). An empty separator splits into user-perceived characters (grapheme clusters), like Characters. A non-string argument leaves the expression unevaluated.

StringSplit(s, pattern): split on each match of a regular expression, with the host dialect's own semantics — including splitting at a zero-width match. Captures are not interleaved into the result; use StringMatchAll for those.

stringSplit("a,b,c", ",")
// ➔ ["a","b","c"]
stringSplit(" one two three ")
// ➔ ["one","two","three"]

Subscript​

(collection<any>, any) -> any

Subscript notation for indexing or compound symbols.

Subscript([10, 20, 30], 2)
// ➔ 20

Subtype​

(subtype: string | type, supertype: string | type) -> boolean

True iff the FIRST operand is a subtype of the second — Subtype("integer", "number") is True, Subtype("number", "integer") is False. This is the same compatibility relation annotations and signatures use. Operands are type values or type text; a quantified (where) type is not comparable and errors.

Subtype("integer", "number")
// ➔ "True"

symbol​

MathJSON Symbol · function

Construct a new symbol with a name formed by concatenating the arguments

symbol("x", 2)
// ➔ "x2"

tail​

MathJSON Tail · (any) -> collection

Return the tail of an expression, the operands of the expression

[tail(max(a, b, c))]
// ➔ [a,b,c]

Text​

(any*) -> string

A sequence of strings, annotated expressions and other Text expressions

Text("Total: ", 42)
// ➔ "Total: 42"

timing​

MathJSON Timing · (value, repeat: integer?) -> tuple<number, value>

Timing(expr) evaluates expr and returns a pair: the time the evaluation took, in microseconds, then the value; read them as Timing(expr)[1] and Timing(expr)[2]. Timing(expr, n) evaluates expr n times (at least 3), drops the fastest and the slowest run, and returns the mean time of the others

timing(2 + 2)[2]
// ➔ 4

to​

MathJSON To · (any, any) -> nothing

Action arrow / mapping (a \to b) — opaque typed head.

replaceAll(x^2 + x, to(x, 3))
// ➔ 12

toLowerCase​

MathJSON ToLowerCase · (string) -> string

ToLowerCase(s): the string s mapped to lower case using the Unicode default (locale-independent) mappings.

toLowerCase("Hello World")
// ➔ "hello world"

toUpperCase​

MathJSON ToUpperCase · (string) -> string

ToUpperCase(s): the string s mapped to upper case using the Unicode default (locale-independent) mappings. The character count can change ("ß" uppercases to "SS").

toUpperCase("straße")
// ➔ "STRASSE"

triangle​

MathJSON Triangle · (any+) -> expression

Triangle primitive — opaque typed head.

triangle(A, B, C)
// ➔ Triangle(A, B, C)

trim​

MathJSON Trim · (string, chars: (character | collection<character | string> | string)?) -> string

Trim(s): remove leading and trailing whitespace (the Unicode White_Space characters).

Trim(s, chars): remove leading and trailing characters that belong to chars — a SET of characters, given as a character, a string (meaning the set of that string's characters) or a collection whose elements each contribute their own characters.

trim(" hi ")
// ➔ "hi"
trim("--hi--", "-")
// ➔ "hi"

trimEnd​

MathJSON TrimEnd · (string, chars: (character | collection<character | string> | string)?) -> string

TrimEnd(s): remove trailing whitespace (the Unicode White_Space characters).

TrimEnd(s, chars): remove trailing characters that belong to chars — a SET of characters, as for Trim.

trimEnd("hi!!", "!")
// ➔ "hi"

trimStart​

MathJSON TrimStart · (string, chars: (character | collection<character | string> | string)?) -> string

TrimStart(s): remove leading whitespace (the Unicode White_Space characters).

TrimStart(s, chars): remove leading characters that belong to chars — a SET of characters, as for Trim.

trimStart("007", "0")
// ➔ "7"

type​

MathJSON Type · (any) -> type

The STATIC type of an expression, as a type value: Type(3) is TypeFrom("integer"). The observer does not evaluate its operand. Recover the text with StringFrom(Type(x)); in a string interpolation a type value renders as its text directly. BREAKING (2026-08-19, ruling R3 of docs/TYPE-SYSTEM.md): the result used to be a STRING, and Type(x) == "some text" is now always False — use x is T, Subtype(Type(x), u), or compare StringFrom text.

type("hi")
// ➔ TypeFrom("string")
type([1, 2, 3])
// ➔ TypeFrom("vector<integer^3>")

typeFrom​

MathJSON TypeFrom · (text: string) -> type

A type expression as a first-class value, constructed from its text: TypeFrom("list<integer>"). The value SETTLES at construction — the text is parsed, reduced, and stored back as its canonical form — so two values built from equivalent spellings ("integer|real", "real|integer") are the same value. == between two type values is mutual subtyping (it also equates an alias with its body); == between a type value and anything else, a string included, is False. Construction never touches the type registry: a forward reference (type X) or an unknown name is an error, not a registration.

TypeFrom("integer | real") == TypeFrom("real")
// ➔ "True"

Typed​

(any, string | symbol) -> unknown

Ascribe a type to an expression. The type is asserted for the type system (ascription, not a check); evaluation is transparent. Used to annotate Function literal parameters and return types.

Typed(2 + 3, "integer")
// ➔ 5

Unevaluated​

(any) -> unknown

Prevent an expression from being evaluated

unicodeScalars​

MathJSON UnicodeScalars · (string) -> list<integer>

A collection of Unicode scalars from a string, same as UTF-32

unicodeScalars("A😀")
// ➔ [65,128512]

utf16​

MathJSON Utf16 · (string) -> list<integer>

A collection of UTF-16 code units from a string.

utf16("A😀")
// ➔ [65,55357,56832]

utf8​

MathJSON Utf8 · (string) -> list<integer>

A collection of UTF-8 code units from a string.

utf8("A€")
// ➔ [65,226,130,172]

Wildcard​

(symbol) -> symbol

Single-expression pattern wildcard.

Wildcard(x)
// ➔ _x

WildcardOptionalSequence​

(symbol) -> symbol

Pattern wildcard matching zero or more expressions.

WildcardOptionalSequence(x)
// ➔ ___x

WildcardSequence​

(symbol) -> symbol

Pattern wildcard matching one or more expressions.

WildcardSequence(x)
// ➔ __x

withRandomSeed​

MathJSON WithRandomSeed · (real | string, any) -> expression

WithRandomSeed(seed, body): evaluate body with a random seed frame

seeded by seed (a finite real or a string). The block replays

identically, while repeated draws WITHIN the frame differ (the n-th

draw is hash(seed, n)).

Scoping is dynamic: the frame is active through user-function calls,

not just lexically inside body. Frames nest and the innermost wins.

Counters are per-frame, so a nested frame does not perturb its

parent's subsequent draws.

Outside any frame, draws are live (non-deterministic).

withRandomSeed(42, [random(1..6), random(1..6), random(1..6)])
// ➔ [5,3,5]