Streams of products and coproducts
Playing with streams of products (pairs) and coproducts (eithers).
Example
Let's say you need to conditionally map events that match a predicate. For example, you want to tag all even and odd numbers with "Even" and "Odd".
You can do it declaratively in 3 composable steps: Partition the stream into evens and odds, tag each respectively, reassemble the stream of tagged values.
// The stream of numbers we want to tagconst nums: Stream<number> = // ... const tagged: Stream<string> =
Using pipelining:
const tagged: Stream<string> = nums |> |> |> unpartition
In detail
// Tag even numbers with "Even", and odd numbers with "Odd"// Ideally, we'd write 3 independent functions, and compose// a solution from them:// 1. an isEven predicate// 2. an "Even" tagging function// 3. an "Odd" tagging function const isEven = x % 2 === 0const tagEven = `Even `const tagOdd = `Odd ` // The stream of numbers we want to tag// We want to end up with a Stream<string>const nums: Stream<number> = // ...
Attempt 1
One approach is to filter
the same stream twice.
// We have to remember to multicast since we'll end up// creating 2 streams derived from nums and merging them.// That kind of "diamond shape" requires multicastingconst multicastNums: Stream<number> = const even: Stream<string> = const odd: Stream<string> = const tagged: Stream<string> =
That works, but has 2 downsides:
- It has to apply the isEven predicate twice to each event
- It has to use multicast, which is easy to forget
Attempt 2
Another approach is to write a conditional lambda.
// We can be explicit and write a new lambda to conditionally// map the eventsconst tagged: Stream<string> =
This is a reasonable solution, but still leaves room for improvement:
- The "extra" lambda requires us to think about each event value one at a time, rather than only thinking about the
nums
as a whole. - The lambda mentions
x
4 times
Can we be even more declarative and compositional?
Using a coproduct stream
// We can solve the problem in 3 declarative steps:// 1. partition the stream of numbers into evens and odds// 2. tag the evens and odds// 3. "unpartition" the stream containing the tagged evens and oddsconst tagged: Stream<string> =
This is completely declarative, with no "extra" lambda, and transforms nums
as a whole, rather than being concerned with each event.