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)--expressionevaluated against the record that playsRole.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
- A role is code, a record is data
- What the trace says
- A record not yet made
- Other samples and other tests, which also covers computing over another specimen, series, and lineage
- API reference