Chapter 3: Aggregating Lists of Timelines
In the previous chapter, we established TL.And
and TL.Or
as robust, Monoid-based combinators for merging the state of two Timeline<bool>
instances. However, many real-world scenarios involve checking conditions across an entire collection of timelines.
This chapter extends our logic to handle lists of boolean timelines. We will demonstrate how the Monoidal nature of our combinators allows for the elegant aggregation of multiple timelines using the standard List.fold
operation, leading us to create the n-ary (multi-input) combinators TL.all
and TL.any
.
The Power of Monoids for Aggregation
Section titled “The Power of Monoids for Aggregation”The key to elegantly aggregating a list of values lies in the fold
operation combined with a Monoid. Recall that (Timeline<bool>, TL.And, TL.TrueTimeline)
and (Timeline<bool>, TL.Or, TL.FalseTimeline)
are both Monoids. This is powerful because List.fold
is designed to work perfectly with Monoidal structures. It takes a binary combining function, an initial value (the Monoid’s identity element), and a list, then iteratively applies the function.
Because our TL.And
and TL.Or
operations are associative and have identity elements, we can use List.fold
to reliably “roll up” a list of timelines into a single result.
TL.all
: Are All Timelines True?
Section titled “TL.all: Are All Timelines True?”Our goal is to take a list<Timeline<bool>>
and produce a single Timeline<bool>
that is true
only if all timelines in the input list are currently true
.
We can define TL.all
by folding over the input list using TL.And
as the combining operation and TL.TrueTimeline
as the initial value.
Construction and Behavior
Section titled “Construction and Behavior”module TL = // Assumes TL.And and TL.TrueTimeline have been defined as: // val And: Timeline<bool> -> Timeline<bool> -> Timeline<bool> // val TrueTimeline: Timeline<bool>
/// Given a list of boolean timelines, creates a new timeline that is true /// if all timelines in the input list are currently true. /// If the input list is empty, it correctly returns TL.TrueTimeline (vacuously true). let all (booleanTimelines: list<Timeline<bool>>) : Timeline<bool> = List.fold TL.And TrueTimeline booleanTimelines
When TL.all [t1; t2; t3]
is called, it constructs a reactive dependency graph equivalent to TL.And (TL.And TrueTimeline t1) (TL.And t2 t3)
. A change to any of the input timelines will propagate through the chain of TL.And
operations and update the final result.
TL.any
: Is At Least One Timeline True?
Section titled “TL.any: Is At Least One Timeline True?”Symmetrically, our goal for TL.any
is to produce a Timeline<bool>
that is true
if any timeline in the input list is currently true
.
We define this by folding with TL.Or
as the operation and TL.FalseTimeline
as the initial value.
Construction and Behavior
Section titled “Construction and Behavior”module TL = // Assumes TL.Or and TL.FalseTimeline have been defined as: // val Or: Timeline<bool> -> Timeline<bool> -> Timeline<bool> // val FalseTimeline: Timeline<bool>
/// Given a list of boolean timelines, creates a new timeline that is true /// if any timeline in the input list is currently true. /// If the input list is empty, it correctly returns TL.FalseTimeline. let any (booleanTimelines: list<Timeline<bool>>) : Timeline<bool> = List.fold TL.Or FalseTimeline booleanTimelines
This builds a reactive graph where the final result is true
if any of the source timelines becomes true
.
Practical Usage
Section titled “Practical Usage”These n-ary combinators allow for clean and declarative aggregation of conditions.
// Assume validationChecks is a list<Timeline<bool>>let allChecksPassed = TL.all validationChecks
// Assume sensorAlarms is a list<Timeline<bool>>let systemAlarm = TL.any sensorAlarms
// As always, for efficiency, compose with distinctUntilChangedlet efficientSystemAlarm = systemAlarm |> TL.distinctUntilChanged
With TL.all
and TL.any
, we now have a complete and robust toolkit for performing logical combinations on any number of boolean timelines. The next step is to apply this toolkit to more complex, real-world data.