Control Structures
Control Structures define how a sequence of expressions is evaluated.
Overview
The flow of a program is controlled by control structures. Control structures are expressions that define how a sequence of expressions is evaluated.
There are three kind of control structures:
- Sequential:
Block, the most common where expressions are evaluated one after the other - Conditional
If,WhichorMatch, where expressions are evaluated depending on the value of a condition or the structure of a value - Iterative
LooporFixedPoint, where expressions are evaluated repeatedly
Sequential Control Structure
Block(expr-1, ...expr-n)
A ["Block"] expression is a sequence of expressions that are evaluated
sequentially.
A new scope is created for the ["Block"] expression. The scope is destroyed
when the ["Block"] expression is finished evaluating. This means that
variables defined in the ["Block"] expression are not accessible outside of
the ["Block"] expression.
The value of the ["Block"] expression is the value of the last expression
expr-n.
If one of the expression in the block is a ["Return"] expression, a
["Break"] expression or a ["Continue"] expression, no more expressions are
evaluated and the value of the ["Block"] is this expression.
["Block"] expressions can be nested as necessary. Scoping is lexical: a
nested scope — an inner ["Block"], an ["If"] branch, or a ["Loop"]
body — can read the enclosing block's variables, and assigning to a variable
declared in an enclosing block updates that binding. A ["Declare"] in the
inner block instead introduces a new variable that shadows the outer one for
the duration of the inner block.
["Block", ["Assign", "c", 5], ["Multiply", "c", 2]]
// ➔ 10
["Block",
["Declare", "counter", "integer"],
["Assign", "counter", 0],
["Loop",
["Block",
["If", ["Not", ["Less", "counter", 5]], ["Break"]],
["Assign", "counter", ["Add", "counter", 1]]
]
],
"counter"
]
// ➔ 5
Conditional Control Structure
If(condition, expr-1)
If the value of condition is the symbol True, the value of the ["If"]
expression is expr-1, otherwise Nothing.
If(condition, expr-1, expr-2)
If the value of condition is the symbol True, the value of the ["If"]
expression is expr-1, otherwise expr-2.
Here's an example of a function that returns the absolute value of a number:
["Function", ["If", ["Greater", "n", 0], "n", ["Negate", "n"]], "n"]
["If"] expressions can be nested as necessary.
Which(condition-1, expr-1, ...condition-n, expr-n)
The value of the ["Which"] expression is the value of the first expression
expr-n for which the corresponding condition condition-n is True.
["Block",
["Assign", "n", -10]
["Which", ["Greater", "n", 0], "n", ["Negate", "n"], "n"]
]
// ➔ 10
A ["Which"] expression is equivalent to the following ["If"] expression:
["If", ["Equal", condition-1, "True"], expr-1,
["If", ["Equal", condition-2, "True"], _expr-2,
... ["If", ["Equal", condition-n, "True"],
expr-n,
"Nothing"
]
]
]
A ["Which"] expression is equivalent to a switch statement in JavaScript or
the Which[] function in Mathematica.
When(expr, condition)
Returns the value of expr when condition evaluates to True, and
Undefined when condition evaluates to False. When condition cannot
be determined, the expression holds unevaluated.
["When"] is the AST head produced by restriction-brace syntax:
expr\{cond\} parses to ["When", expr, cond]. It is also useful directly
for masking values where a predicate does not hold. The braces may be
separated from the base expression by spacing commands
((1-t)^2(1+2t)\ \{t\ge0\}\{t\le1\} attaches both restrictions).
["When", ["Square", "x"], ["Greater", "x", 0]]
// Evaluates to x^2 when x > 0, Undefined otherwise.
Stacked restrictions canonicalize to a single When with an And
predicate:
["When", ["When", "x", ["Greater", "x", 0]], ["Less", "x", 10]]
// Canonicalizes to:
["When", "x", ["And", ["Greater", "x", 0], ["Less", "x", 10]]]
Downstream simplification, interval intersection, and compilation operate on
the canonical form, so source variants (stacked braces or a single brace
with \wedge) are interchangeable.
When compiled to JavaScript or GLSL, When(e, cond) emits a ternary
(cond ? e : NaN). This makes When suitable for plot-domain masking.
Match(subject, case-1, ...case-n)
Structural pattern matching: the value of the ["Match"] expression is the
value of the first case whose pattern matches the structure of subject
(and whose guard, if any, is True).
Each case is a ["MatchCase"] expression:
["MatchCase", pattern, body]["MatchCase", pattern, guard, body]
The pattern uses the same wildcards as
patterns and rules: "_n"
captures one value, "__n" a sequence, "___n" an optional sequence, and
"_" matches anything without capturing. The guard and body reference
captures by their bare name ("n" for "_n").
["Match", ["List", 3, 4],
["MatchCase", ["List", "_a", "_b"], ["Add", "a", "b"]]
]
// ➔ 7
Unlike ["Which"], which evaluates boolean conditions and stays unevaluated
while a condition cannot be decided, ["Match"] inspects the structure
of the subject and always selects a case: a symbolic subject that is not
structurally equal to a pattern falls through to the next case.
["Match", "x",
["MatchCase", 0, "'zero'"],
["MatchCase", "_", "'other'"]
]
// ➔ "other" — x could be 0 semantically, but is not structurally 0
A guard must evaluate to True for the case to be selected; False or an
undecidable guard falls through to the next case:
["Match", 5,
["MatchCase", "_n", ["Greater", "n", 0], "'positive'"],
["MatchCase", "_", "'other'"]
]
// ➔ "positive"
Two auxiliary heads may appear inside a pattern:
["Pin", expr]matches the value ofexpr, evaluated when the match is performed, rather than its structure. Use it to match against a constant (["Pin", "Pi"]) or the current value of a variable.["Alternatives", p-1, ..., p-n], as a case's pattern, matches if any alternative matches. The alternatives share the case's guard and body and must not contain named captures.
["Match", 2,
["MatchCase", ["Alternatives", 1, 2, 3], "'small'"],
["MatchCase", ["Pin", "Pi"], "'pi'"],
["MatchCase", "_", "'big'"]
]
// ➔ "small"
If no case matches, the value of the expression is
["Error", "'match-no-case'", subject] — an ordinary error value.
Matches over constant cases are dispatched in constant time, and fixed-shape
List/Tuple/Dictionary patterns are matched without invoking the
general pattern matcher. When compiled to JavaScript, constant cases emit a
comparison chain or a switch statement, and fixed-shape destructuring is
supported; symbolic patterns (such as ["Add", "_a", 1]) cannot be
compiled and fail with an error rather than producing incorrect code.
In Cortex, ["Match"] is written with the match keyword:
match x { 0 => "zero"; 1 | 2 | == Pi => "small or pi"; _ => "other" }.
Loops
Loop(body)
Repeatedly evaluate body until the value of body is a ["Break"] expression,
or a ["Return"] expression.
["Break"]exits the loop immediately. The value of the["Loop"]expression is the value of the["Break"]expression.["Return"]exits the loop and returns the value of the["Return"]expression.
To exit the loop, a ["Break"] or ["Return"] expression must be evaluated.
Loop with only a body argument is equivalent to a while(true) in
JavaScript or a While[True, ...] in Mathematica.
Loop(body, element-1, element-2, ...)
Iterates over one or more ["Element", _name_, _collection_] clauses,
evaluating body once per combination, for effect. The value of the
["Loop"] expression is Nothing — unlike Comprehension (see below),
Loop does not accumulate or return a list of results.
Bindings are evaluated as nested loops, outermost = first Element clause.
Later clauses see earlier bindings in scope, so a clause's collection can
depend on a name bound by an earlier clause (dependent collections work).
["Loop", ["Print", ["Square", "_"]], ["Element", "_", ["Range", 5]]]
// ➔ 1 4 9 16 25 (printed); the Loop expression evaluates to Nothing
Loop with a body and a single Element clause is equivalent to a
forEach() in JavaScript. It is somewhat similar to a Do[...] in
Mathematica.
Inside body:
["Break"], optionally with a value, exits the loop immediately. The value of the["Loop"]expression is the value ofBreak, orNothingif none was provided.["Continue"]skips to the next iteration.["Return"]exits the loop and propagates out of the enclosing["Function"]expression.
Bound names do not leak into the enclosing scope.
FixedPoint(body, initial-value)
FixedPoint(body, initial-value, max-iterations)
Assumes body is an expression using an implicit argument _.
Apply body to initial-value, then apply body to the result until the result
no longer changes.
To determine if a fixed point has been reached and the loop should terminate,
the previous and current values are compared with Equal.
Inside body, use a ["Break"] expression to exit the loop immediately or
Return to exit the enclosing ["Function"] expression.
LaTeX Syntax for Control Structures
Control structures are expressed in LaTeX using keywords. Each keyword can be written three equivalent ways, which may be mixed freely:
\keyword{if}— the preferred form: stays in math mode and renders with symmetric keyword spacing.\text{if}— the conventional form.\operatorname{if}(or\mathrm{if}) — operator-name spelling.
Multi-word keywords are a single token, e.g. \keyword{for all}. (\keyword{...}
requires the rendering environment to define the \keyword command; \text{...}
and \operatorname{...} render everywhere.)
Inline If
\keyword{if} x > 0 \keyword{then} x \keyword{else} -x
Parses to ["If", ["Greater", "x", 0], "x", ["Negate", "x"]].
The else branch is optional.
where Bindings
x^2 + y^2 \keyword{where} x \coloneq 3,\; y \coloneq 4
Parses to a ["Block"] with variable declarations, assignments, and the body
expression as the return value.
for Loops
\keyword{for} i \keyword{from} 1 \keyword{to} 10 \keyword{do} i^2
Parses to ["Loop", ["Power", "i", 2], ["Element", "i", ["Range", 1, 10]]].
This is an imperative loop, evaluated for effect: its value is Nothing.
for Comprehensions
A trailing for clause produces a list comprehension:
(x, y) \keyword{for} x = [1...3], y = [1...x]
Parses to:
["Comprehension",
["Tuple", "x", "y"],
["Element", "x", ["Range", 1, 3]],
["Element", "y", ["Range", 1, "x"]]]
The trailing for keyword binds looser than , and =, so the body
expression ((x, y) above) is parsed before the keyword fires, and the
bindings are parsed as comma-separated name = expr pairs after it.
Multiple bindings produce a Cartesian product (or a dependency-shaped
iteration when later bindings reference earlier ones). See the
Comprehension definition in the
Collections
reference for full semantics.
Restriction Braces
A trailing \{cond\} after an expression masks the value by a predicate:
f(x)\left\{0 < x < 2\right\}
Parses to ["When", ["f", "x"], ["Less", 0, "x", 2]].
When the condition is True, the expression evaluates to its left operand;
when False, it evaluates to Undefined. This is distinct from a set
literal (standalone \{1, 2, 3\} continues to parse as a Set); the
disambiguation is positional — trailing braces after a complete expression
attach as a When restriction.
Stacked restrictions chain and canonicalize:
x\left\{x > 0\right\}\left\{x < 10\right\}
Parses to ["When", "x", ["And", ["Greater", "x", 0], ["Less", "x", 10]]].
The serializer round-trips this canonical form back to stacked braces.
Semicolon Blocks
Semicolons (; or \;) act as statement separators to build Block
expressions:
x \coloneq 3;\; x^2 + 1
Parses to ["Block", ["Declare", "x"], ["Assign", "x", 3], ["Add", ["Power", "x", 2], 1]].
Controlling the Flow of Execution
To exit a function, use Return.
To control the flow of a loop expression, use Break and Continue.
Return(value)
Interrupts the evaluation of a ["Function"] expression. The value of the
["Function"] expression is value.
The ["Return"] expression is useful when used with functions that have
multiple exit points, conditional logic, loops, etc...
Here's a contrived example of a function that returns the sign of a number:
[
"Function",
[
"Block",
["If", ["Greater", "x", 0], ["Return", 1]],
["If", ["Less", "x", 0], ["Return", -1]],
0
],
"x"
]
Break()
Break(value)
Break is a registered operator. When in a loop, exit the loop immediately.
The value of the enclosing ["Loop"] expression becomes value, or
Nothing if not provided.
Outside a loop, Break is inert.
Continue()
Continue is a registered operator. When in a loop, skip to the next
iteration of the loop. Continue takes no argument.
Outside a loop, Continue is inert.