Skip to content
docs
Arxo ↗

Expressions and quantities: const, function, arithmetic, aggregates

For LLMs7 sections

In one sentence: these constructs answer the question “how to compute a value without leaving the norm”, with no external computation. Take them when a norm needs a number and a measure: a named value (const), a named computation (function), a threshold and a rate (comparison and arithmetic), a unit and a dimension (magnitudes), a summary over a set (aggregates) and explicit rounding (round/div_round).

Values are computed inside the norm: named constants and functions, exact arithmetic over quantities, and explicit rounding — no silent conversions.

Instead ofSelection rule
Expression vs judgmentA threshold in the body (n >= 10) is an expression over data: the machine computes. A disputable reading of a norm (“manifestly excessive”, “good cause”) is the judgment channel: a human decides, the machine awaits the verdict. A number is an expression, an assessment is a judgment
round vs div_roundRounding a finished value — round(v, precision, mode). Division with rounding — div_round(a, b, precision, mode) as one operation over the exact quotient, not a composition of two: round(x / y, …) fails with INEXACT_DIVISION before round ever sees it
collect vs collect allCounting entities — collect (Set, distinct values). Summarizing charges — always collect all (List, positions): two equal incomes in a Set are one element, and sum silently undercounts. Statics cannot see the difference — guard it with a test on two equal facts
const vs case factProgram immutables (rate, threshold, benchmark) — const. Mutable legal facts are assert with origin, not a constant: case facts are not modelled with const

Grammar: comparisons, quantifiers, collections (EBNF verbatim from the grammar)

Section titled “Grammar: comparisons, quantifiers, collections (EBNF verbatim from the grammar)”
Show syntax reference

Comparisons are a formula conjunct (==/!= only in formulas; in removed, unary ! removed):

Grammar
comparison = relational_expression,
comparison_operator,
relational_expression ;
comparison_operator = relational_operator | "==" | "!=" ;
relational_expression = additive_expression,
[ relational_operator, additive_expression ] ;
(* Errata E-0070: оператор `in` снят из relational_operator — членство
выражается квантором `exists … in … where` §52; парсер формы не разбирал. *)
relational_operator = "<" | "<=" | ">" | ">=" ;
additive_expression = multiplicative_expression,
{ ( "+" | "-" ), multiplicative_expression } ;
multiplicative_expression
= unary_expression,
{ ( "*" | "/" | "mod" ), unary_expression } ;
(* Errata E-0070: унарный `!` снят — отрицание пишется словом `not`
(proposition) либо инверсией сравнения; лексер `!` вне `!=` отвергает. *)
unary_expression = [ "+" | "-" ], postfix_expression ;

Quantifiers over a collection:

Grammar
quantifier = existential_quantifier | universal_quantifier ;
existential_quantifier = "exists", identifier, "in", expression,
"where", formula ;
universal_quantifier = "forall", identifier, "in", expression,
"satisfies", formula ;

Collections — collect (Set) and collect all (List):

Grammar
comprehension_expression
= "collect", [ "all" ], identifier, ":", type_ref,
{ ",", identifier, ":", type_ref },
"where", formula ;

Package research.expressions.rounded_share: a rate constant, a percent and explicit rounding. Salary 100 000 KZT, bonus — a quarter under HALF_UP, monthly share — a twelfth under the policy.

Arxo Law
const BONUS_RATE: Decimal = 25 percent;
rule Bonus strict {
for e: Employee; for s: Money;
when salary(e, s);
then bonus(e, round(s * BONUS_RATE, 2, "HALF_UP"));
}
rule MonthlyShare strict {
for e: Employee; for s: Money;
when salary(e, s);
then monthly_share(e, div_round(s, 12.0, 2, "HALF_UP"));
}

Case facts: salary(urn:case:research:expressions:ali, 100000 KZT) with origin case_input. Query: evaluate truth(bonus(…, 25000 KZT)).

Observed engine answer (installed law, semantics law.core/0.2):

Output
law test research.expressions.rounded_share: мир research.expressions.rounded_share
ok [research.expressions.rounded_share#authored] tests/01-bonus-computed.lawtest / urn:query:research-expressions-01
ok [research.expressions.rounded_share#authored] tests/02-no-salary.lawtest / urn:query:research-expressions-02
итого: 2 проверено, 2 прошли, 0 не прошли, 0 не исполнены; код 0

law engine check — check OK, no warnings. Sensitivity: removing the salary fact changes the expectation from TRUE_ONLY to NEITHER (tests/02-no-salary.lawtest) — the example is not vacuous.

Nearest wrong outcome (verified by reading the dividend table): divisor 12 instead of 12.0 — Money / Integer is not in the dividend table, statics answer LDC-E2108. The reverse would be the mistake: expecting the integer to silently “promote” to decimal — there are no implicit conversions.

  • Law: England-and-Wales intestacy (package gb.statute.administration_of_estates_act_1925, England and Wales) — function fixed_net_sum() -> Money = FixedNetSum: an A3 parameter read with a witness at every use; nearby — the norm’s exact fraction (v - c - sum) * (1 / 2) (see corpus-forms.md).
  • Standard/protocol: the SI unit dictionary (package units.si, units pack) — pub unit kg dimension Mass scale 1 / 1: the unit dictionary all magnitude arithmetic rests on; conversion — only convert, tag attachment — with_unit.
  • Religion: zakat on money (package fiqh.zakat, Islamic jurisprudence) — div_round(w, 40.0, 2, "HALF_UP"): one fortieth as explicit division under a policy, not the w * 0.025 approximation.
  • Science: medication ordering (package med.medication_order, medical corpus) — const MillilitreEdinitsa: Quantity = 1 ml: a named benchmark making the “volume → dimensionless number” transition explicit; the rule below multiplies a dose by a counter n * q (Integer * Quantity).
  • Teaching case: research.expressions.counted_threshold — the same device in miniature: the FULL_CREW threshold as const, the count(collect …) >= FULL_CREW guard in the body, the sum(collect all …) summary over positions; three equal 50 000 KZT payouts guard the silent-undercount trap.

Table — observed runs of this directory’s examples (installed law, semantics law.core/0.2):

FactsQuestionAnswerWhy
salary(ali, 100000 KZT)bonus(ali, 25000 KZT)TRUE_ONLY100000 × 0.25 exact, round under HALF_UP
no factsbonus(ali, 25000 KZT)NEITHERsalary premise not established; the expression does not invent the base
salary(ali, 100000 KZT)monthly_share(ali, 8333.33 KZT)TRUE_ONLYdiv_round(s, 12.0, 2, "HALF_UP") — one operation over the exact quotient
three members, payouts 3 × 50000crew_bonus(crew1)TRUE_ONLYcount == 3 >= FULL_CREW
two memberscrew_bonus(crew1)NEITHERcount >= 3 guard false; below threshold — silence
  • In proof a term value is published computed: the result stands there (25000 KZT), not the expression tree; argument supports enter the rule-application proof.
  • In why_not an unfired guard gives NEITHER: the expression does not name the silence cause — it names the missing premise.
  • The monthly_share(ali, 8333.33 KZT) line was separately verified on a scratch copy of the package (same law test, a third trial file, 3/3): 100000 / 12 = 8333.333…, HALF_UP at two places gives 8333.33. The trial is not in the shipment — it adds no sensitivity to the example.
  • The crew_payroll summary is crew-sensitive in the same order: removing a member removes their payout from collect all, and the previously true total becomes NEITHER — the summary does not “remember” the departed.
  • Execution refusals (TYPE_ERROR, CURRENCY_MISMATCH, INEXACT_DIVISION, DIVISION_BY_ZERO, EMPTY_AGGREGATE) are presented as case issues, not as a whole-conclusion refusal: the rule does not fire, the rest continue.
  • BOTH in an if-term condition gives CONFLICTED_CONDITION, NEITHER — keeps missing/external issues and picks no branch.

Outcomes (earlier scenarios):

FactsQuestionAnswerWhy
two incomes of 100,000 eachhousehold_income(h, 200 000 KZT)TRUE_ONLYcollect all — two positions
samehousehold_income_distinct(h, 100 000 KZT)TRUE_ONLYcollect — one value
samehousehold_income_distinct(h, 200 000 KZT)NEITHERsilent undercount: neither refusal nor warning
two membershousehold_size(h, 2); member_share(h, a, 1 / 2); half_share(h, a)TRUE_ONLYcount; 1 / 2 and 2 / 4 are one Rational
tenure 12 / 3senior(a) / senior(b)TRUE_ONLY / NEITHERguard n >= 10; below threshold — silence
grade(a, Senior); tenure 12senior_by_grade(a); even_years(a)TRUE_ONLYmember equality; 12 mod 2 == 0
signature 23:01+01:00signed_at_moment(a)TRUE_ONLYguard == 22:01Z — the same moment
salary 100,000bonus(a, 25 000 KZT); monthly_salary(a, 8 333.33 KZT)TRUE_ONLYround, div_round under HALF_UP
a is seniorany_member_senior(h) / all_members_senior(h)TRUE_ONLY / NEITHERexists / forall over collect
member c with no incomehousehold_income(h2, 0 KZT)RUNTIME_ERROREMPTY_AGGREGATE: an empty sum is not zero

A term value is asked with a DATA query (evaluate -7 mod 3 → 2, Euclidean remainder; evaluate 7 / 2 → Rational 7/2); refusals — with an expect issue(CODE) observation (CURRENCY_MISMATCH, INEXACT_DIVISION, DIVISION_BY_ZERO, TYPE_ERROR).

Money, shares, and functional lookup — the second half of the earlier expression scenarios (every row below is a documented outcome):

Term / factsQuestionAnswerWhy (test)
—pow(2, 10) + abs(-24) + min(3, 5)DATA 1051std terms (“power, absolute, minimum”)
—100 KZT * (1 / 4) + div_round(100 KZT, 3.0, 2, "HALF_UP")DATA 58.33 KZTexact share plus division under a policy (“money for a share under a policy”)
—7 / 2DATA Rational 7/2, no INEXACT_DIVISIONexact fraction (“7 / 2 is an exact Rational”)
—1 + 1 / 2TYPE_ERRORcounter and share do not add (“Integer + Rational”)
—1000 KZT + 5 USD, 100 KZT / 5 USDCURRENCY_MISMATCHdifferent currencies (“money in different currencies” ×2)
—100 KZT / 3.0, 100 KZT * (1 / 3)INEXACT_DIVISIONinexact quotient without a policy (“Money / Decimal inexact”, “Money * Rational inexact”)
—15 mg / 1 mgDATA 15.0same-unit share (“Quantity of one unit”)
—15 mg / 1 kgTYPE_ERRORdifferent units (“Quantities of different units”)
—7 mod 0DIVISION_BY_ZEROzero (“mod by zero”)
—250 KZT / 1000 KZTDATA 0.25 — a share, not money(“Money / Money of one currency is a share”)
—25 percentDATA exact Decimal 0.25(“percent is an exact Decimal”)
—convert(...)DATA 3000 mconversion only by explicit conversion (“convert is explicit conversion”)
—let/if/matchDATA (e.g. 6)layers and analysis (“let and if are expressions”, “layers through let”)
salary/fundprorated_salary, salary_per_worked_month, two_thirds_of_salary, member_bonusTRUE_ONLYone rounding over the exact result (“months out of twelve”, “variable divisor”, “two thirds”, “same proportion”)
head + incomehead_incomeTRUE_ONLYthe set’s single value (“only: the head’s single income”)
head + two incomeshead_incomeNEITHER, RUNTIME_ERRORtwo distinct values — conflict, not choice (“only: two distinct values”)
  1. Summarizing charges via collect instead of collect all — silent undercount on equal values (pitfalls.md, item 1).
  2. Dividing money by a whole number (div_round(s, 12, …)) — LDC-E2108 (pitfalls.md, item 4).
  3. round without precision/mode — LDC-E2116; percent of type Percentage instead of Decimal — LDC-E2101 (pitfalls.md, item 5).
  4. Mixing currencies and units (1000 KZT + 5 USD, 500 kg > 100 m) — check stays silent, execution answers CURRENCY_MISMATCH/TYPE_ERROR (pitfalls.md, item 6).
  5. Double rounding (round over a div_round total) — divergence from the norm (pitfalls.md, item 8).
  6. Bare domain constant in a term — LDC-E1330, constant cycle — CYCLIC_CONSTANT (pitfalls.md, items 9–10).

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.