Skip to content

14. Records

A strength is sometimes reported against another specimen's: as a share of a reference sample's strength, or compared with an earlier test of the same sample. The formula then reads a value from a record other than the one being evaluated, and a trace that lists only numbers cannot say whose each one was. This chapter divides a specimen's strength by a reference sample's, read from that sample's record, and shows what the trace says about each value and what happens when the reference has not been tested yet.

The program is self-contained: it declares its own quantities and does not build on the previous chapters' programs.

A value from another sample or an earlier test

The specimen's strength and the reference sample's are the same quantity, each held by its own record. The ratio of the two is a pure number. A formula never names a sample: it names a role, a type the program declares, here Reference. Which sample plays that role is data, decided when the records are put together:

using Strength = formula::Quantity<struct StrengthTag, "f_c", "compressive strength", unit::Megapascal>;
using StrengthRatio =
    formula::Quantity<struct StrengthRatioTag, "r", "strength as a share of the reference strength", unit::One>;

/// The role the reference sample plays.
struct Reference
{
};

formula::from_record<Reference>(expression) evaluates expression against the record that plays Reference. Outside it, the formula reads the record being evaluated:

constexpr auto ratio = formula::yields<StrengthRatio>(var<Strength> / formula::from_record<Reference>(var<Strength>));

Each record holds its own environment. formula::record<Role>(key, environment) makes one, and its key is a sample and a test, formula::sample_id and formula::test_id: two separate types, so a swapped pair does not compile. formula::ThisRecord is the library's role for the record being evaluated. formula::record_context holds the records by role:

constexpr auto specimen = formula::environment(formula::Measured<Strength> { 30 });
constexpr auto referenceSample = formula::environment(formula::Measured<Strength> { 32 });

constexpr auto records = formula::record_context(
    formula::record<formula::ThisRecord>(formula::record_key(formula::sample_id(17), formula::test_id(5)), specimen),
    formula::record<Reference>(formula::record_key(formula::sample_id(23), formula::test_id(3)), referenceSample));

The context is this record's environment as well, so formula::checked_explain and everything else that takes an environment takes it. A formula that reads from a role the context does not bind does not compile. An earlier test of the same sample is read the same way: a second role, played by a record with the same sample key and another test key.

Rendered, the read from the other record names its role:

std::println("{} = {}", formula::symbol_of<StrengthRatio>(), formula::render(ratio));

Where each value came from

report explains the ratio over a context, prints the trace, then the ratio:

/// Prints how the ratio was calculated over @p context, then the ratio.
/// False when it failed.
template <typename Context>
bool report(Context const& context)
{
    auto const explained = formula::checked_explain(ratio, context);
    if (!explained)
    {
        std::println("no ratio: {}", explained.error().error);
        return false;
    }
    std::print("{}", formula::render_trace(explained->trace, { .maxSteps = 10 }));
    std::println("{} = {}", formula::symbol_of<StrengthRatio>(), explained->outcome);
    return true;
}

The specimen's strength is 30 MPa and the reference sample's 32 MPa:

std::println("the reference sample tested:");
if (!report(records))
    return 1;

The trace keeps, on every step, the record it was read from. The specimen's own strength is line 1, with no record named. Line 2 is the reference sample's strength, and names the role and both keys, sample 23, test 3: two tests of one sample share the sample key, so a sample alone could name either. Line 3 is the read from the other record, and line 4 divides the two: 30/32 is 15/16, the exact decimal 0.9375.

A record not yet made

The reference sample may not have been tested yet. Its record then keeps its role, and has no key and no values. Record<Role, Environment>::unbound() makes it, and has the same type as the record formula::record<Reference>(...) makes from referenceSample, so one formula takes one context type whether the reference has been tested or not:

auto const notYetTested = formula::record_context(
    formula::record<formula::ThisRecord>(formula::record_key(formula::sample_id(17), formula::test_id(5)), specimen),
    formula::Record<Reference, decltype(referenceSample)>::unbound());
std::println("the reference sample not yet tested:");
if (!report(notYetTested))
    return 1;

The read gives no value: line 2 says no record is bound, and the ratio is (not measured), never zero. A ratio of zero would read as a specimen with no strength.

Output

r = f_c / (f_c of Reference)
the reference sample tested:
1. f_c = 30 MPa
2. f_c = 32 MPa, from record Reference (sample 23, test 3)
3. #2 from record Reference (sample 23, test 3) = 32 MPa
4. #1 / #3 = 15/16
r = 0.9375
the reference sample not yet tested:
1. f_c = 30 MPa
2. from record Reference (no record bound) = (not measured)
3. #1 / #2 = (not measured)
r = (not measured)

Summary

  • formula::from_record<Role>(expression) -- expression evaluated against the record that plays Role.
  • formula::record<Role>(formula::record_key(sample, test), environment) -- a record, its key and its values.
  • formula::record_context(records...) -- the records by role; it is this record's environment, so everything that takes an environment takes it.
  • formula::Record<Role, Environment>::unbound() -- a record not yet made: a read from it gives no value.

Further reading