Custom Functions and Symbols
The MathJSON Standard Library is a
collection of definitions for symbols and functions such as Pi
, Add
,
Sin
, Power
, List
, etc...
In this guide we discuss how to augment the MathJSON Standard Library with your own definitions.
Introduction
When a new symbol or function is encountered in an expression, the Compute Engine will look up its definition in the set of known identifiers, including the Standard Library.
Automatic Declaration
If the identifier is found, the definition associated with it will be used to evaluate the expression.
If the identifier is not found, an automatic declaration will be made of the
identifier as a symbol of type unknown
, or a more specific type if the
context allows it.
To provide a more explicit definition for the identifier, you can define it
using a LaTeX expression, or an explicit declaration using the ce.declare()
or ce.assign()
methods.
Declarations are Scoped
The declaration of an identifier is done within a scope. A scope is a hierarchical collection of definitions.
Definitions Using LaTeX
The simplest way to define a new symbol or function is to use LaTeX.
For example, to define a new symbol m
with a value of 42
, use the
following LaTeX expression:
ce.parse("m := 42").evaluate();
ce.parse("m").evaluate().print();
// ➔ 42
Note: the assignment expression must be evaluated to take effect.
To define a new function f
that multiplies its argument by 2
, use
the following LaTeX expression:
ce.parse("f(x) := 2x").evaluate();
ce.parse("f(3)").evaluate().print();
// ➔ 6
The \mapsto
operator is an alternative syntax to define a function:
ce.parse("f := x \\mapsto 2x").evaluate();
ce.parse("f(3)").evaluate().print();
// ➔ 6
To define multiletter symbols, use the \operatorname{}
command:
ce.parse('\\operatorname{double}(x) := 2x').evaluate().print();
ce.parse('\\operatorname{double}(3)').evaluate().print();
// ➔ 6
Note: you can also use the \mathrm{}
or \mathit{}
commands to wrap
multiletter symbols.
The LaTeX identifiers are mapped to MathJSON identifiers. For example,
the LaTeX \operatorname{double}
is mapped to the MathJSON identifier double
.
console.info(ce.parse('\\operatorname{double}(3)').json);
// ➔ ["double", 3]
Explicit Declarations
To have more control over the definition of a symbol or function, use
the ce.declare()
and ce.assign()
methods.
When declaring a symbol or function, you can specify the type of the symbol or signature of the function, its value or body, and other properties.
// Declaring a symbol "m"
ce.declare("m", { type: "integer", value: 42 });
// Declaring a function "f"
ce.declare("f", {
signature: "number -> number",
evaluate: ce.parse("x \\mapsto 2x"),
});
Defining a Symbol
To prevent the value of a symbol from being changed, set the constant
property to true
:
ce.declare("m_e", {
value: 9.1e-31,
constant: true,
});
If you do not provide a type
property for a symbol, the type will be
inferred from the value of the symbol. If no type and no value are
provided, the type will be unknown
.
To provide the type of the identifier, without associating it with a value, use the following syntax:
ce.declare("n", "integer");
As a shorthand, a symbol can be declated by assigning it a value using ce.assign()
:
ce.assign("m", 42);
If the symbol was not previously defined, this is equivalent to:
ce.declare("m", { value: 42 });
Alternatively:
ce.box("m").value = 42;
Defining a Function
To define a function, associate an evaluate
handler, which
is the body of the function, with the function declaration.
ce.declare("double", { evaluate: ce.parse("x \\mapsto 2x") });
The evaluate handler can be either a MathJSON expression as above or a JavaScript function.
ce.declare("double", { evaluate: ([x]) => x.mul(2) });
The signature of the evaluate
handler is (args[], options)
, where:
args
: an array of the arguments that have been applied to the function. Each argument is a boxed expression. The array may be empty if there are no arguments.options
: an object literal which includes anengine
property that is the Compute Engine instance that is evaluating the expression and anumericApproximation
property that is true if the result should be a numeric approximation.
Since args
is an array, you can use destructuring to get the arguments:
ce.declare("double", { evaluate: (args) => args[0].mul(2) });
// or
ce.declare("double", { evaluate: ([x]) => x.mul(2) });
In addition to the evaluate
handler the function definition can include
a signature
type that describes the arguments and return value of the
function.
ce.declare("double", {
signature: "number -> number",
evaluate: ([x]) => x.mul(2),
});
See FunctionDefinition
for more details on the other handlers and
properties that can be provided when defining a function.
To define a function without specifying a body for it, specify
the signature of the function as the second argument of ce.declare()
or
use the "function"
type.
ce.declare("double", "function" );
Functions that do not have an evaluate handler remain unchanged when evaluated.
You can set the body of the function later using ce.assign()
:
When using ce.assign()
to define a function, the value can be a JavaScript
function, a MathJSON expression or a LaTeX expression.
ce.assign("double", ([x]) => x.mul(2));
ce.assign("double", ["Function", ["Multiply", "x", 2], "x"]);
ce.assign("double",ce.parse("x \\mapsto 2x"));
Defining Multiple Functions and Symbols
To define multiple functions and symbols, use the ce.declare()
method
with a dictionary of definitions.
The keys to ce.declare()
(m
, f
, etc...) are MathJSON
identifiers, not LaTeX commands. For example, if you have a symbol α
, use
alpha
, not \alpha
ce.declare({
m: { type: "number", value: 5 },
f: { type: "function" },
g: { type: "function" },
Smallfrac: {
signature: "(number, number) -> number",
evaluate: ([x,y]) => x.div(y),
},
});
To assign multiple functions and symbols, use the ce.assign()
method with
a dictionary of values.
ce.assign({
"m": 10,
"f(x)": ce.parse("x^2 + x + 41"),
"g(t)": ce.parse("t^3 + t^2 + 17"),
});