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
| Qualifier | Does | Input |
|---|---|---|
uppercase | Convert to UPPERCASE | String |
lowercase | Convert to lowercase | String |
trim | Strip leading and trailing whitespace | String |
replace | Substring replacement; with { find: …, replace: … } | String |
clip | Truncate to a width; with 40 | String |
lines | Split into a list of lines | String |
markdown | Render markdown to HTML | String |
lines drops the trailing newline's phantom element, so counting lines is
lines then length and nothing else.
Collections and numbers
| Qualifier | Does | Input |
|---|---|---|
length / count | Character or element count | String, List, Object |
take | First N elements; with 3 | String, List |
unique | Remove duplicates, first-seen order wins | String, List |
join | Join into a string; with { separator: ", " } | List |
sum | Total of a numeric collection | List |
avg / average | Arithmetic mean | List |
random | A random element, or a random Int below a bound | String, List, Int, Float |
fixed | Settle on a presentation scale: N decimal places, 2 by default; with { places: 3 } | Int, Float, Currency, String |
identity | Return the input unchanged | Any |
Hashing, encoding and escaping
| Qualifier | Does | Input |
|---|---|---|
hash / sha256 | SHA-256 digest, hex-encoded | Any |
html-escape | Escape & < > " ' for HTML | String |
json-escape | Escape for a JSON string literal | String |
url-encode / url-decode | Percent-encode a query value | String |
base64-encode / base64-decode | Standard Base64 | String |
base64url-encode / base64url-decode | URL-safe Base64, as JWTs use | String |
URLs
Addresses are parsed and rewritten rather than pasted together (ARO-0019):
| Qualifier | Does |
|---|---|
url-resolve | Resolve a relative URL against a base — Compute the <abs: url-resolve> from <href> with { base: <page> }. |
url-parts | scheme, host, port, path, query, fragment |
url-defragment / url-normalize | Strip #frag; give one spelling per address |
url-encode / url-decode | Percent-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 &lt;.
Dates and sets
| Qualifier | Does | Input |
|---|---|---|
date | Parse an ISO 8601 string into a date | String |
format | Render a date | Date |
distance | Distance between two dates; to <other> | Date |
intersect | Elements in both; with <other> | List, Object |
difference | Elements in A not in B; with <other> | List, Object |
union | All 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:
-
Sorting, reversing and element access are actions.
Sort the <ranked> for the <users> by "score".,Reverse the <backwards> for the <items>.,Extract the <first-item: first> from the <items>. -
A result type uses
as, because the qualifier slot is already spoken for by the operation:Compute the <n> as Float from <s>.This is also whereas Currencylives, somoney,currency,decimalandexactare redirected to it by name;round,roundedandprecisionare redirected tofixed.
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.