Chapters

Types and formatting

Every value in a Catena sequence has a type — a decimal number, a whole number, a date, a length of time, a label, and so on. You rarely have to write the type down: Catena works it out from what you type. But knowing the small set of types, and why they exist, explains why money stays exact, why a date will not quietly turn into a number, and how the AS operator lets you both convert a value and format it for display.

This chapter teaches the type system first, then the literals you write to create values, then the two jobs of AS. If you are new to Catena, read Language basics first; this chapter builds on the arrow assignment (->) shown there.


The friendly type set

Catena presents a curated set of types with plain-English names. You will only ever meet these eight:

Type What it holds Typical use
Num An exact decimal number — the default Money, prices, everyday arithmetic
Real An approximate number that may carry a long fractional tail Results of trigonometry, logarithms, and irrational roots
Int A whole number Counts, indexes, quantities of items
Bool A truth value: TRUE or FALSE Yes/no conditions and comparisons
Date A point in time An invoice date, a deadline
Duration A length of time, such as 30 days Gaps between dates, loan terms
Text A label, a code, or typed-in prompt text "Net price?", a currency code like "USD"
Bag The one container — a list of values A column of figures, [150, 200, 99.50]

A value's type is fixed the first time you assign to it, and every later use of that name is treated as that type. This is why a simple typo cannot silently create a second variable: reading a name you never assigned is flagged as an error before the sequence runs, with a "did you mean...?" hint.

Two of these types deserve their own sections, because the difference between them is the most consequential idea in Catena: Num and Real.


Why money is exact by default

Type 0.10 + 0.20 into most computer languages and you get 0.30000000000000004. That is not a bug in the language; it is a limitation of binary floating point, which cannot represent tenths and hundredths precisely. For a calculator whose audience includes accountants, that is unacceptable — it silently corrupts money.

So Catena's default number type, Num, is exact decimal. It stores 0.10 as exactly one tenth, and:

  • Number literals you type are Num.
  • Adding, subtracting, multiplying, and dividing Num values gives an exact Num.

That means money arithmetic is exact from end to end. A hundred line items summed as Num will not drift by a fraction of a cent.

Real is the other kind of number — an approximate value, the sort that carries a long, possibly non-terminating tail. Catena only reaches for Real when a result genuinely cannot be exact:

  • Transcendental functionsSIN, COS, TAN, LOG, LN, EXP — return Real.
  • Roots return Real only when the answer is irrational. SQRT(9) is the exact Num 3; SQRT(2) is a Real, because its decimal never ends.

The rule of thumb: your prices, totals, and counts stay exact, and only the values that mathematics itself cannot pin down become approximate.


Widening and narrowing

The three numeric types form a ladder — Int, then Num, then Real — from most precise to most general. Catena moves values up the ladder automatically, and only ever moves them down when you ask in writing.

Widening is automatic and silent (Int -> Num -> Real), because it never loses anything you meant to keep. A whole number is already a valid decimal; a decimal is already a valid approximate number. So a function that expects a Real happily accepts a Num, and one that expects a Num accepts an Int. You do not write anything.

Narrowing is never silent. Going the other way — Real -> Num, or Num -> Int — throws away information (a fractional tail, or the digits after the decimal point), so Catena refuses to do it behind your back. You must request it explicitly with AS (below), and the value is rounded according to the sequence's active rounding setting.

SQRT(2)      -> diagonal      # a Real, 1.41421356...
diagonal AS Num -> rounded    # explicit narrowing to an exact decimal

This is the safeguard that keeps a stray approximation from quietly polluting an exact money total.


Optional and enforced typing

Catena uses gradual typing: by default types are inferred and never written, so a sequence reads like ordinary arithmetic. When you want more control, you can opt in at two levels:

  • On a single assignment, add AS and a type to lock what a value becomes:

    prompt_value -> age AS Int      # this input must be a whole number
  • On a whole sequence, turn on strict mode with the setting ESET STRONGTYPE ON. In strict mode, types must be declared rather than inferred, and any implicit narrowing is rejected. It is the belt-and-braces choice for a sequence you intend to share, where you would rather catch a type mistake up front than discover it in the numbers.

Declared types are checked when the sequence is validated — before it ever runs — so mistakes surface early.


Writing values: the literals

A literal is a value written directly in the source. Catena reads the shape of what you type and gives it the right type automatically.

Kind How you write it Becomes
Number 42, 3.14, -5, 1_000_000, 2.5e3 Num
Money $399, €20, 1234.56 USD a Num amount in that currency
Quantity with unit 5 kg, 30 days, 90 km/h a number carrying a unit
Date / time 2026-05-31, 12:30, March 12 2026 Date
Text "Net price?" Text
Boolean TRUE, FALSE (or YES, NO) Bool
Bag [150, 200, 99.50] Bag

A few points worth noting:

  • Digit grouping. The underscore in 1_000_000 is purely cosmetic — it makes long numbers readable and is ignored by the calculator. 1_000_000 and 1000000 are the same value.
  • Money is a unit. $399 and 1234.56 USD are the same idea written two ways: an amount attached to a currency. Because the amount underneath is a Num, currency arithmetic is exact. Units and currencies are covered fully in Units and quantities.
  • Dates take durations, not bare numbers. To move a date, add a Duration: today + 5 days. Adding a plain number to a date — today + 5 — is an error, because "plus five what?" is genuinely ambiguous; Catena stops and asks rather than guessing. 30 days is a Duration literal; 5 days added to a Date gives a new Date.

The AS operator: convert or format

AS answers a single question — "give me this value, but as a ___." What fills the blank decides which of two different jobs AS performs:

You write value AS X where X is... What happens Result
a type (from the type set above) the value is converted to that type a value of the new type
a format (a rendering name) the value is rendered as human-readable text Text

The distinction matters:

  • Converting to a type changes the value. someDate AS Int turns a date into a whole number you can compute with (a count of seconds, by default). diagonal AS Num narrows an approximate Real to an exact decimal.
  • Formatting does not change the value — it produces a Text rendering, the sort of thing you hand to DISPLAY, PRINT, or OUT. The underlying value is untouched; you are only choosing how it looks.

Catena keeps this unambiguous by making type names and format names two separate sets with no overlap. So AS DateLong can only mean "format as a long date" (there is no type called DateLong), and AS Int can only mean "convert to a whole number."

The built-in formats. In v1 these are bare names that take no parameters:

Value being rendered Available formats
Date DateLong, DateShort, DateISO, Time, DateTime
number Money, Percent

For finer control of how a decimal prints — a fixed number of places, for instance — use the sequence's display-precision and rounding settings (ESET ROUNDING) rather than a format name.


Worked examples

Formatting a date three ways. The same Date value can be displayed in whichever style suits the output. None of these change the date; each produces a piece of Text:

2026-05-31 -> invoiceDate
invoiceDate AS DateLong    # "Saturday, 31 May 2026"
invoiceDate AS DateISO     # a machine-style date, e.g. 2026-05-31
invoiceDate AS DateShort   # a short, locale-aware date

Rendering a number as a percentage. A ratio held as a plain number can be shown as a percentage for display, again without altering the stored value:

0.2 -> vatRate
DISPLAY vatRate AS Percent    # shown to the reader as a percentage

Adding money exactly. Because money amounts are exact Num values, small denominations sum without drift — the classic 0.10 + 0.20 problem simply does not occur:

$0.10 + $0.20 -> total     # exactly 0.30, not 0.30000000000004
DISPLAY total AS Money     # rendered as currency text for the reader

Narrowing an approximate result on purpose. A transcendental result is a Real. If you need it as an exact decimal — to fold it into a money calculation, say — narrow it explicitly, which rounds per the active rounding setting:

LN(10)          -> growth     # a Real, 2.302585...
growth AS Num   -> rounded    # explicit, deliberate narrowing

Converting a date to a number. When you genuinely need arithmetic on a date — the gap between two instants as a raw count, for example — convert it to an Int. This is allowed precisely because you asked for it in writing; only silent date-to-number coercion is dangerous, and Catena never does that:

invoiceDate AS Int -> stamp   # a whole-number time stamp

The pattern across all of these is the same: let Catena infer types while you type, reach for AS when you want to convert a value or dress it up for display, and trust that money and counts stay exact unless you deliberately step off that path.


See also