31 Folds
A fold takes the values reached by a path and collapses them to one value. The following expression counts the sessions for each course:
u.course.(code, session.Σcount as sessionCount)
Without the fold, session reaches the sessions of a course one at a time. With
Σcount, those reached sessions are counted and the count is output as
sessionCount.
The same idea works for numeric values. The following expression sums the maximum marks available from a course's exams:
u.course.(code, assessment.exam.maxMark.Σsum as examMarks)
The path before the fold reaches the exam maximum marks for the current course. The fold turns those marks into one number.
Common folds
These standard folds cover many common cases:
| Fold | Example | Use |
|---|---|---|
Σcount |
session.Σcount |
Count reached values. |
Σexists, Σempty |
prerequisite.Σempty |
Test whether any values were reached. |
Σsum, Σmean, Σstddev |
assessment.project.estimatedHours.Σmean |
Summarise numeric values. |
Σmin, Σmax |
session.time.startMinute.Σmin |
Find an extreme value. |
Σall, Σany |
enrolment.current.Σall |
Summarise Boolean values. |
Collecting reached values
The fold .Σ collects the reached values as a set. The following expression gives
each course with the set of codes for its prerequisites:
u.course.(code, prerequisite.code.Σ as prerequisiteCodes)
This is useful when the path should remain attached to the current course as one set-valued output, rather than producing one output for each prerequisite code.
Folds after filters
Because filters are path tails, a fold can summarise the values that remain after filtering. The following expression counts long teaching sessions for each course:
u.course.(
code,
session.time.[endMinute-startMinute > 60].Σcount
as longSessionCount)
The filter reaches only the time slots whose duration is greater than sixty minutes. The fold counts those filtered time slots.
Inline folds
Named folds such as Σsum are ordinary fold definitions. An inline fold writes
the reduction directly:
u.course.(
code,
assessment.exam.durationMinutes.Σ{ 0 ; total d ↦ total + d }
as examMinutes)
The first part, 0, is the initial accumulator. The step
total d ↦ total + d says how to update the accumulator for each reached duration.
This example is equivalent to using Σsum, but inline folds are useful when the
reduction is specific to the query.
An inline fold can also use the first reached value as its seed:
u.course.(
code,
session.time.startMinute.Σ{
first m ↦ m ;
earliest m ↦ if m < earliest then m else earliest }
as firstStart)
This is the shape used by folds such as Σmin and Σmax.
Empty inputs
Some folds have a value even when no values are reached. For example, Σcount
gives 0, Σexists gives false, and Σempty gives true.
Other folds need at least one reached value. For example, Σmean, Σmin and
Σmax are not defined on an empty input. When that matters, use a filter or a
separate test such as Σexists to say when the folded value is required.
Composability
Folds compose with the path tails from earlier chapters. Navigation chooses what
values can be reached, filters remove unwanted values, calculations can produce
values to fold, and as names the folded output.
u.course.(
code,
assessment.project.[groupWork].estimatedHours.Σsum
as groupProjectHours)
Here the path reaches each course, enters its project assessments, keeps only the group projects, reaches their estimated hours, sums those hours, and names the result.