Skip to content
docs
Arxo ↗

Exercise 8. The largest of the unpaid

For LLMs2 sections

An exercise for the collections and aggregates page. On the page, the unpaid-order total counts as an aggregate with an explicit all.

Setup. Derive for a reader the largest unpaid order’s sum. Two unpaid orders, at 1,000 and 2,500 tenge, yield the answer 2500 KZT. A reader without orders has no answer, and no runtime error either: the question answers NEITHER with an empty problem list.

Hint. An aggregate over an empty collection is an error, not zero. What must stand in the norm’s condition so the aggregate is not computed in vain?

Below is the solution. Try it yourself first.

Arxo Law
language "law.core" version "0.2";
package tutorial.archive version "0.14.1";
namespace "urn:law:tutorial:archive";
entity Person;
entity CopyOrder;
relation copy_order(o: CopyOrder, p: Person) kind empirical;
relation order_amount(o: CopyOrder, amount: Money) kind empirical { key(o); }
relation order_paid(o: CopyOrder) kind empirical;
relation registered_reader(p: Person) kind institutional;
relation unpaid_order(o: CopyOrder) kind institutional;
relation largest_unpaid(p: Person, amount: Money) kind institutional { key(p); }
rule UnpaidOrder strict {
for o: CopyOrder;
for p: Person;
when copy_order(o, p) and not order_paid(o);
then unpaid_order(o);
}

The maximum norm. The guard in the condition counts unpaid orders and lets the norm through only on a nonempty collection:

Arxo Law
rule LargestUnpaid strict {
for p: Person;
when registered_reader(p) and count(collect o: CopyOrder where copy_order(o, p) and unpaid_order(o)) > 0;
then largest_unpaid(p, max(collect all a: Money, o: CopyOrder where copy_order(o, p) and unpaid_order(o) and order_amount(o, a)));
}

all is here for the same reason as in the page’s sum: two orders at an equal sum are two orders, and the maximum must not depend on whether equal values collapsed. For a maximum this does not change the answer, but the collection form is a data decision, not an operation one, and it must not vary from aggregate to aggregate.

FactsQuestionAnswer
two unpaid: 1,000 and 2,500largest_unpaid(ivanova, 2500 KZT)TRUE_ONLY
two unpaid: 1,000 and 2,500largest_unpaid(ivanova, 1000 KZT)NEITHER
reader without orderslargest_unpaid(ivanova, 0 KZT)NEITHER, no problems
without the count(...) > 0 guard, reader without orderssameNEITHER, an EMPTY_AGGREGATE problem

The fourth row is a counterfactual: with the guard cut out the engine answers the empty-aggregate error. It does not drop the whole document, but it appears in the problem list, and the consumer must see it.

What the exercise teaches. An empty collection is not zero. Every norm with an aggregate needs a nonempty guard or a separate empty-case norm, like TotalDueNothing on the page. The choice between them is a question for the text: does the act say anything about a reader without orders.

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

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