So you have multiple operations that you want to perform on a list, you’re a good lad (or lass) and you’re not using a giant for loop; you’re feeling clever today and you’ll be using functional operators like map, filter, and reduce.

But the list is relatively big, and even though it looks elegant, iterating on it 10 times is not exactly up for a performance award.

[1, 2, 3, ..., 100000]
  .map(it => it * 2)
  .filter(it => it % 3 == 0)
  .moreOps(...)
  .filter(...)
  .moreOps(...)
  .filter(...)
  .reduce((acc, it) => acc + it);

No, I’m not here to talk about the order of operators

It’s important, but it’s not always applicable. Instead, I want to discuss transducers, not the energy transformation devices, but the concept itself.

You want to have your cake and eat it too—use the operators but with a single iteration. It’s quite an old problem, and so is its solution. Today we’ll focus on just filter.

Closures, not the ones you know

Let's take a look at filter

It takes a predicate function that receives an item and returns a boolean value. Based on this returned value the item is either kept or discarded.

const evenOnly = [1, 2, 3, 4, 5, 6].filter(it => it % 2 == 0) // [2, 4, 6]

We can create an object that keeps track of each predicate used on an array, and this is actually a perfect solution for implementing the same concept in other operators.

Without changing filter implementation or introducing a new filter function, we can use a simpler approach for the sake of this post, and it’s a very powerful concept in many other applications as well: Closures.

You might know that a closure is basically capturing the enclosing environment in the body of an inner environment/ function. But this is not the closure we are talking about here; Being able to manipulate/combine functions to produce another function with the same characteristics is also called a closure ¯\(ツ)

An operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation.

Structure & Interpretation of Computer Programs

Allow me to elaborate, what’s a predicate?

type Predicate<T> = (item: T) => boolean;

A simple boolean value of true can be transformed to another boolean value of false by different types of manipulation. Can we do the same to a function like a predicate?

let us look at different ways to manipulate a boolean:

false && false; // false
false || false; // false
false && true; // false
false || true; // true
!false // true

And much more. Can we apply and, or, not to our predicates? Why of course we can! Why do you think I’m writing this? We just need to make the right representation of the effect of these operators:

const and =
  <T>(predicate1: Predicate<T>) =>
  (predicate2: Predicate<T>) =>
  (item: T) =>
    predicate1(item) && predicate2(item);
const or =
  <T>(predicate1: Predicate<T>) =>
  (predicate2: Predicate<T>) =>
  (item: T) =>
    predicate1(item) || predicate2(item);
const not =
  <T>(predicate: Predicate<T>) =>
  (item: T) =>
    !predicate(item);

It’s utilising the same primitive boolean operations, and at the same time combines the predicates. Well, it combines their results. Which is what we care about.

Most importantly, calls to and, or, not also return a predicate! This allows to endlessly combine results of our combinations! This is the power of (the other) closures!

Why are we returning a function that accepts the other predicate instead of taking it as another parameter? Glad you asked!

Why did we do this again?

Because now that you can combine predicates together, you can have the same elegance of separating your filters while making a single call to filter:

const even = (n) => n % 2 ==0;
const productOfThree = (n) => n % 3 == 0;
const evenAndProductOfThree = and(even)(productOfThree);
[3, 6, 9, 12]
  .filter(evenAndProductOfThree); // [6, 12]

Or on the fly, since evenAndProductOfThree is also a predicate, we can reuse it

[3, 6, 9, 12]
  .filter(not(evenAndProductOfThree)); // [3, 9]

You can combine and accumulate as much predicates as you want, it’ll all be run in a single iteration. But it also allows you to maintain the elegance of separating your predicates and combining them as building blocks to build the most suitable filter you need for your data.

What did you mention transducers for?

Well, it’s true this is not exactly a transducer, rather an introduction to the other meaning of closures so that in the next post, transducers will be our focus in sha’ Allah.