Exercise 8. The largest of the unpaid
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.
Solution
Section titled “Solution”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:
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.
| Facts | Question | Answer |
|---|---|---|
| two unpaid: 1,000 and 2,500 | largest_unpaid(ivanova, 2500 KZT) | TRUE_ONLY |
| two unpaid: 1,000 and 2,500 | largest_unpaid(ivanova, 1000 KZT) | NEITHER |
| reader without orders | largest_unpaid(ivanova, 0 KZT) | NEITHER, no problems |
without the count(...) > 0 guard, reader without orders | same | NEITHER, 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.