← Back to Documentation

Computations

Compute derives a new value from an existing one. The qualifier picks the operation; the result name is yours.

Compute belongs to the OWN semantic role: it transforms data inside a feature set without reaching outside it. Reach for Create when you are making a new literal, and Compute when you are deriving something from a value you already have.

Compute the <total> from <price> * <quantity>.
Compute the <digest: sha256> from the <payload>.
Compute the <safe: html-escape> from the <comment>.

Qualifier-as-name

Write Compute the <length> from the <message>. and the result name is the operation. That is fine until you need two of them in one feature set — names are immutable, so the second would have nowhere to go. Split them with a qualifier: the base becomes the variable name, the qualifier selects the operation.

(* Name and operation are the same word *)
Compute the <length> from the <message>.

(* Name and operation separated — now you can have both *)
Compute the <first-length: length> from the <first-message>.
Compute the <second-length: length> from the <second-message>.

The qualifier namespace is closed

A Compute qualifier must resolve to a built-in from the tables below, a plugin qualifier (handle.qualifier), a chain (a|b), or a date offset (-7d). Anything else is an error that names the closest match:

error: Unknown Compute qualifier 'lenght'
  hint: Did you mean 'length'?
  hint: Run `aro actions --qualifiers` for the full set

aro check rejects unknown qualifiers too, so a green check means the qualifier exists. Namespaced names and chains are accepted at check time and resolved at run time, because aro check does not load plugins. Run aro actions --qualifiers for the live set, plugins included.

Text

QualifierDoesInput
uppercaseConvert to UPPERCASEString
lowercaseConvert to lowercaseString
trimStrip leading and trailing whitespaceString
replaceSubstring replacement; with { find: …, replace: … }String
clipTruncate to a width; with 40String
linesSplit into a list of linesString
markdownRender markdown to HTMLString

lines drops the trailing newline's phantom element, so counting lines is lines then length and nothing else.

Collections and numbers

QualifierDoesInput
length / countCharacter or element countString, List, Object
takeFirst N elements; with 3String, List
uniqueRemove duplicates, first-seen order winsString, List
joinJoin into a string; with { separator: ", " }List
sumTotal of a numeric collectionList
avg / averageArithmetic meanList
randomA random element, or a random Int below a boundString, List, Int, Float
fixedSettle on a presentation scale: N decimal places, 2 by default; with { places: 3 }Int, Float, Currency, String
identityReturn the input unchangedAny

Hashing, encoding and escaping

QualifierDoesInput
hash / sha256SHA-256 digest, hex-encodedAny
html-escapeEscape & < > " ' for HTMLString
json-escapeEscape for a JSON string literalString
url-encode / url-decodePercent-encode a query valueString
base64-encode / base64-decodeStandard Base64String
base64url-encode / base64url-decodeURL-safe Base64, as JWTs useString

URLs

Addresses are parsed and rewritten rather than pasted together (ARO-0019):

QualifierDoes
url-resolveResolve a relative URL against a base — Compute the <abs: url-resolve> from <href> with { base: <page> }.
url-partsscheme, host, port, path, query, fragment
url-defragment / url-normalizeStrip #frag; give one spelling per address
url-encode / url-decodePercent-encode a single query value

url-normalize is what makes a crawler's "have I seen this?" answerable: two spellings of one address compare equal after it.

Regex captures

The pattern comes from the same by /pattern/flags clause Split uses. captures binds the first match's groups as a record — named groups under their names, numbered ones under "1", "2", …, and the whole match under match. all-captures binds a list of those records:

Compute the <pair: captures> from <line> by /(?<key>\w+)=(?<value>.*)/.
Compute the <pairs: all-captures> from <line> by /(?<key>\w+)=(?<value>.*)/.

A non-match binds an empty record (or an empty list) rather than failing — the same call ARO makes for a Retrieve that matches nothing, so guard on the result. A group that took part in no match is absent rather than empty.

Escaping and templates

A template whose path ends .html or .htm escapes what it prints. .tpl, .txt and .md do not — so a .tpl emitting HTML still needs html-escape. Never hand-escape into an escaping template, or your reader sees &amp;lt;.

Dates and sets

QualifierDoesInput
dateParse an ISO 8601 string into a dateString
formatRender a dateDate
distanceDistance between two dates; to <other>Date
intersectElements in both; with <other>List, Object
differenceElements in A not in B; with <other>List, Object
unionAll unique elements; with <other>List, Object

See Working with Dates and Set Operations for the longer treatment of each.

Arithmetic

Compute also takes an expression directly, with the usual + - * / % and the precedence you expect.

Compute the <subtotal> from <price> * <quantity>.
Compute the <taxed> from <subtotal> * 1.19.
Compute the <total: fixed> from <taxed>.

Money

What you reach for with money is not a qualifier but the as clause. Float is binary floating point and cannot represent 2.40, so three of them is 7.199999999999999. as Currency makes the same statement exact base-10 arithmetic:

Compute the <line-total> as Currency from <qty> * <price>.   (* 7.20 *)
Compute the <with-fee> from <line-total> + 1.05.             (* still exact *)

A Currency is a number, not a rendering: it serialises as a JSON, CSV or YAML number, and in an HTTP body, at its own scale — 99.95, never 99.94999999999999 and never "99.95". Exactness is contagious, as the second statement above shows, so an amount is computed once and every aggregate downstream agrees about it. as Decimal is the same format under a different name. The format carries no currency code; that lives in ARO-0014's Money schema, and the two compose.

fixed then settles the presentation scale — two places in a CSV, four in a rate table. On a Float it is a repair, rounding through the decimal spelling so the stored Double is the one nearest 99.95; on a Currency there is nothing to repair, so it is a rescale and fixed of 7.2 is the exact amount 7.20. Round each amount once, at the precision that amount actually has.

Chaining

Qualifiers chain with |, each stage resolving like a lone qualifier:

Compute the <slug: lowercase|trim> from the <title>.

Plugin qualifiers

Plugins contribute qualifiers under their own handle, so there is never a collision with a built-in:

Compute the <pick: collections.pick-random> from the <items>.
Compute the <ranked: stats.sort> from the <numbers>.

See Writing Extensions for how a plugin declares them.

Two things that are not qualifiers

Both of these are common enough that the check-time diagnostic names them:

Specification

Encoding qualifiers are specified in ARO-0019 Standard Library §3.1, collection and text qualifiers in §3.2, and the closed namespace in §3.3.


← The Basics | Data Pipelines →