examples/readings/
Summarise a week of sensor readings with map, filter, fold, find, any and all.
- 173 lines
- 2 modules
These functions transform readings, select them, combine them into a result, or check conditions. They are written in Heroes and called with method syntax, placing the value before the function name. The first run exposed a compiler defect: it treated two chained generic calls as the same call when identifying which types they used.
Run it
5
98.1
19.6
thu 20.3
true
true
true
false
mon 18.2, tue 19.7, thu 20.3, fri 17.8, sat 22.1
981
exit 0 · 10 lines
The program
# A week of sensor readings, summarised — and the only program in this
# repository that calls the library through UFCS.
#
# Corpus and regression material (CLAUDE.md §9), and the gap it closes was
# measured rather than guessed. Over `examples/`, `selfhost/`
# and `tests/`: `map`, `filter`, `fold`, `find`, `any` and `all` — the six the
# spec's own § Built-ins calls *"written in Heroes"* — were called by exactly
# ONE program in the repository, `tests/golden/run/library-higher-order.hero`,
# and by nothing in `examples/` at all. `all` was called by nothing anywhere.
#
# **And the UFCS form was called by nothing, full stop: zero uses of
# `xs.map(f)` in every `.hero` file in the repository.** The spec illustrates
# the library with exactly that spelling — *"Top-level functions are values:
# `xs.fold(0, add)`"* — so the form the document teaches the feature with had
# no program behind it. This file writes every call that way on purpose.
#
# What the program does is ordinary, which is the point: read a week of
# readings, drop the ones the sensor did not answer, total and average them,
# look one up, and ask three questions about the whole set.
use scale
record Reading
day: str
raw: i64
# A sensor that failed answers -1 rather than a temperature in tenths.
constant MISSING: i64
0 - 1
function week() -> [Reading]
return [
Reading(day: "mon", raw: 182)
Reading(day: "tue", raw: 197)
Reading(day: "wed", raw: MISSING)
Reading(day: "thu", raw: 203)
Reading(day: "fri", raw: 178)
Reading(day: "sat", raw: 221)
Reading(day: "sun", raw: MISSING)
]
function answered(r: Reading) -> bool
return r.raw != MISSING
function too_warm(r: Reading) -> bool
return r.raw > 200
function blazing(r: Reading) -> bool
return r.raw > 400
function plausible(r: Reading) -> bool
return r.raw > 0 && r.raw < 500
function tenths_of(r: Reading) -> i64
return r.raw
# `acc` and `item` are `fold`'s own words for these two, and a function handed
# to `fold` names them that way since M-named-callbacks: two parameters of one
# type at a position no call site can reach, so the type is where the names live.
function add(acc: i64, item: i64) -> i64
return acc + item
function label(r: Reading) -> str
return r.day + " " + scale.tenths(r.raw)
function main()
all_week = week()
# `filter` then `map`, both through the dot, which is the shape the spec
# writes and nothing in this repository had.
good = all_week.filter(answered)
print(good.len())
total = good.map(tenths_of).fold(0, add)
print(scale.tenths(total))
print(scale.tenths(total / good.len()))
# `find` gives a `T?`, so both sides are written out: a hit, and a miss
# that no reading in the week can satisfy.
print(label(good.find(too_warm).must()))
print(good.find(blazing).is_err())
# The three questions about the whole set, which is where `any` and `all`
# earn their names — and the third is the one that fails, on purpose.
print(good.any(too_warm))
print(good.all(plausible))
print(all_week.all(answered))
# `map` over a function returning `str`, so B is not A.
print(join(good.map(label), ", "))
# `range` builds the indices, and `fold` over them sums the ones the
# sensor answered — the same total by a different road.
days = range(from: 0, to: all_week.len())
print(days.fold(0, plus_if_answered))
function plus_if_answered(acc: i64, item: i64) -> i64
one = week()[item]
if answered(one)
return acc + one.raw
return acc
test "the six agree with the answers computed by hand"
good = week().filter(answered)
assert good.len() == 5
assert good.map(tenths_of).fold(0, add) == 981
assert good.any(too_warm)
assert good.all(plausible)
assert !week().all(answered)
assert !good.any(blazing)
test "find answers a T? on both sides"
good = week().filter(answered)
assert good.find(too_warm).must().day == "thu"
assert good.find(blazing).is_err()
assert good.find(blazing).default(Reading(day: "none", raw: 0)).day == "none"
test "map may change the element type, and every one of the six survives an empty array"
good = week().filter(answered)
assert good.map(label).len() == 5
empty: [Reading] = []
assert empty.map(tenths_of).fold(0, add) == 0
assert empty.filter(answered).len() == 0
assert !empty.any(too_warm)
# Vacuously true, and that is the edge worth pinning: `all` over nothing
# answers true even for a predicate no element could satisfy.
assert empty.all(blazing)
assert empty.find(too_warm).is_err()
test "the two roads to the total agree"
good = week().filter(answered)
days = range(from: 0, to: week().len())
assert days.fold(0, plus_if_answered) == good.map(tenths_of).fold(0, add)
scale.hero · 38 lines
# Tenths of a degree, printed the way an instrument prints them.
#
# Its own module for one reason worth stating: it is the only thing in this
# program that does not take a `Reading`, so keeping it here is what lets
# `main.hero` be read as a list of library calls and nothing else.
#
# It also carries this program's `use` path — `examples/readings/` is flat, so
# `use scale` is the bare form, and the path form lives in `examples/shapes/`.
# `182` becomes `18.2`, and a negative one keeps its sign in front of the
# whole number rather than inside it. Integer division truncates toward zero
# (spec § Failure), so the two halves are computed from the absolute value and
# the sign is put back by hand — which is the whole reason this is a function
# and not an expression at the call site.
function tenths(value: i64) -> str
if value < 0
return "-" + tenths(0 - value)
whole = value / 10
part = value % 10
return whole.to_str() + "." + part.to_str()
test "a reading prints as tenths"
assert tenths(182) == "18.2"
assert tenths(221) == "22.1"
assert tenths(981) == "98.1"
test "a whole number keeps its point, and zero is 0.0"
assert tenths(200) == "20.0"
assert tenths(0) == "0.0"
test "a value under one tenth keeps the leading zero"
assert tenths(7) == "0.7"
test "the sign goes in front of the number, not inside it"
# `-0.7` and not `-0.-7`, which is what a single expression over a
# negative value produces once `%` truncates toward zero.
assert tenths(0 - 7) == "-0.7"
assert tenths(0 - 182) == "-18.2"
fingerprint sha256 499b258e99e9 · 3 files · index.json
Next: shapes. All the examples. ← Previous: deck
Run one yourself: the four commands. Learn the language, or read the full specification in one file.