Chapters
Sequences and functions
A sequence is a Catena program you write once and reuse. Where a single calculation answers one question, a sequence packages a whole procedure — take some inputs, do the working, hand back a result — under a name you choose. A vatTotal sequence adds tax to a list of prices; a monthlyPayment sequence turns a loan's terms into a repayment figure. Once written, a sequence is a reusable building block: you run it from other sequences, and — the payoff this chapter builds towards — you call it by name inside any ordinary expression, even a spreadsheet cell, exactly as if it were one of the calculator's built-in functions.
This chapter teaches the idea in three steps. First, how to define a sequence with parameters and a result. Then how to run one from another with CALL. Finally, how a saved sequence becomes a callable function you can write as =MySeq(A1) anywhere an expression is accepted. If you have not met Catena's building blocks yet, read Control flow and Commands and standard library first; this chapter assumes you can already write the lines that go inside a sequence.
What a sequence is
Each sequence is a self-contained unit. Think of it the way a mathematician thinks of a function f(x): it has inputs, it does its work in private, and it produces an output. A sequence's variables are local — they exist only while it runs and are invisible to everything outside it. Two sequences never share variables, and there is no global scratchpad they both write to. A sequence talks to the outside world in exactly two ways:
- In: through its parameters — the values you pass when you call it.
- Out: through what it returns — a single value, or a value bag (a labelled collection of named outputs).
This isolation is deliberate. Because a sequence cannot reach into another sequence's variables, you can reason about one on its own, hand it to a colleague, and trust it to compute the same way wherever it runs. (The one thing that does travel between sequences is the engine settings — rounding policy and the like — which a caller passes down to everything it calls. See Commands and standard library.)
Defining a sequence
A sequence has a name, a list of parameters in brackets, a body, and a RETURN that names its result. The keyword is SEQUENCE (you may abbreviate it to SEQ), closed by END:
SEQUENCE vatTotal(prices)
SUM(prices) * 1.23 -> total
RETURN total
ENDRead that as: given a bag of prices, add them up, apply 23% VAT, store it in total, and return total. The -> total is an assignment — it names the running result. RETURN total is what the caller receives.
Parameters. List the inputs the sequence needs inside the brackets, separated by commas. A parameter may carry an optional type — SEQUENCE tax(amount AS Num, rate AS Num) — which the calculator checks for you and which powers the editor's hints. Types are optional unless the sequence turns on strict typing; see Commands and standard library.
Returning. A sequence returns either one value (RETURN total) or a value bag — several named outputs bundled together (RETURN [net, vat, gross] and friends). A sequence with no RETURN at all returns an empty bag. That is fine when the sequence exists to display or record something, but — as you will see below — a sequence you intend to use as a function must end with a RETURN, because a function has to hand back a value.
The SEQUENCE ... END wrapper is optional. Every saved sequence already carries its name, its parameters, and its return shape as saved information, so the editor can present them as a form and fill the header for you — you never have to type SEQUENCE ... END by hand. The textual header is just an optional view of what is already recorded. One consequence worth knowing: if a written header gives a name that differs from the saved name, the two simply become aliases for the same sequence, and you can call it by either.
Calling a sequence with CALL
Inside one sequence, you run another with CALL. The result comes back into a bag you name with ->:
CALL vatTotal(prices) -> resultCALL name(args) -> resultBag runs the named sequence with the arguments you supply and stores whatever it returned in resultBag. Arguments may be plain values or bags, and because a returned bag is itself a value, you can grow it across a chain of calls — take what one sequence gave you, add to it, and pass it on:
CALL function_A -> faValues
PROMPT "How old are you?" -> userAge
userAge -> faValues["user_age"] # add a named entry to the bag
CALL function_B (faValues, userAge)This is the ordinary, explicit way to compose sequences: each CALL is a visible step in the working, and the result lands in a variable you named.
Recursion and the depth limit
A sequence may call itself, directly or through a chain of other sequences that eventually call back. This is recursion, and Catena permits it — but with a guard rail. Left unchecked, a sequence that keeps calling itself would spiral forever and exhaust the calculator's memory. To prevent that, recursion is depth-bounded: there is a cap on how many calls deep the nesting may go, and reaching it stops the run with a clear, friendly error rather than a crash.
The cap is the MAXDEPTH engine setting, so an author who genuinely needs deep recursion can raise it for their sequence. Recursion is rarely the natural choice in Catena — the aggregate commands (SUM, AVERAGE, and the rest) and the REPEAT loop usually read more clearly — but it is available when a problem is genuinely recursive.
Using a saved sequence as a function
Here is where sequences stop being a Catena-only tool and become part of the whole calculator. Any saved sequence can be called by name inside an ordinary expression — not just from another sequence with CALL, but anywhere the calculator accepts an expression. The clearest example is a spreadsheet cell:
=vatTotal(A1:A6)When the sheet evaluates that cell, it finds your vatTotal sequence by name, runs it with the range A1:A6 as its prices argument, and drops the returned value into the cell — exactly as if vatTotal were a built-in like SUM. The same works in any expression that reaches the engine, so a sequence you wrote for one purpose becomes vocabulary you can reuse everywhere. A handful of rules govern how this behaves, and each one is there for a reason:
Built-ins always win — a sequence can never shadow them. When the calculator resolves a name in an expression, its own built-in functions and the spreadsheet functions are checked first; your sequences are checked only after. So if you name a sequence SUM or sqrt, the built-in of that name keeps its meaning and your same-named sequence becomes unreachable by that name. The sequence is still saved and still runs by CALL — but you cannot reach it as =SUM(...), because SUM already means the built-in. The practical advice: give a sequence you intend to call as a function a distinct name that no built-in already owns.
It must return a value. A sequence used as a function has to produce something for the expression to use, so it must reach a RETURN. If the body runs to the end without ever executing a RETURN, the call fails rather than quietly handing back some leftover value. (Contrast this with CALL, which is happy to receive an empty bag.)
Errors surface as the expression's own error. Whatever goes wrong inside the sequence is reported in the calling expression's terms. In a spreadsheet cell, a divide-by-zero inside the sequence shows as #DIV/0 in the cell; an unknown name inside it shows as #NAME, and so on — the cell reports the real cause, not a vague failure.
Nesting is depth-capped. A sequence used as a function can call other sequences, which can call others, and a sequence can call itself. As with CALL recursion, that nesting is capped — by default 64 levels deep. Exceeding the cap faults the call instead of overflowing; in a spreadsheet cell the fault shows as #VALUE. This protects a sheet from a runaway or accidentally circular sequence.
A network-backed sequence can be marked refreshable. Most sequences are pure calculation: the same inputs always give the same result, so a cell that calls one need only recompute when its inputs change. A sequence that reaches out over the network — fetching a live exchange rate, say — is different: its answer can change even when nothing on the sheet did. Such a sequence can be marked refreshable (volatile). Cells that call a refreshable sequence are recomputed whenever you refresh the sheet, so live data stays current. A pure sequence stays un-refreshable and is not needlessly recomputed. See Spreadsheet for how refresh works across a sheet.
Sequence names must be plain identifiers, not reserved words. A sequence name follows the same rules as any Catena name — letters (including Unicode), digits, and underscores, not starting with a digit — and it cannot be a reserved Catena keyword such as RETURN, IF, WHEN, or CALL. Those words already have a fixed meaning in the language, so they are not available as names.
A worked example
Suppose you write a sequence that computes a price with VAT applied and returns the gross figure:
SEQUENCE grossPrice(net, rate)
net + net * rate -> gross
RETURN gross
ENDIt takes two inputs — a net price and a tax rate — and returns the gross. Save it under the name grossPrice.
Calling it from another sequence with CALL. From any other sequence, run it and capture the result:
CALL grossPrice(150, 0.23) -> result
DISPLAY "Gross: " result["gross"]The call runs grossPrice with net = 150 and rate = 0.23, and result receives its returned value. Reading a named entry out of the returned bag (result["gross"]) gives you the figure to display.
Calling it as a function from a spreadsheet cell. With grossPrice saved, you can use it directly in the sheet. Put a net price in A1 and a rate in B1, then in C1 write:
=grossPrice(A1, B1)The cell finds grossPrice, runs it with the two cell values as its arguments, and shows the gross price. Change A1 or B1 and C1 recomputes, just as any formula would. The same sequence now serves two roles from one definition: an explicit step inside your Catena programs, and a reusable function across your spreadsheets.
Because the name grossPrice matches no built-in, it resolves to your sequence with no ambiguity. Had you named it sum, the cell's =sum(...) would have meant the built-in SUM instead — the reason to keep function-style sequence names distinct.
To keep, organise, and share the sequences you build, see the Library.
Related chapters
- Control flow — the
IF, loops, and jumps that make up a sequence body. - Commands and standard library — the verbs, aggregates,
RETURN, value bags, and engine settings used above. - Spreadsheet — where
=MySeq(...)calls live and how refresh recomputes them. - Library — saving, naming, and sharing the sequences you write.