Wells, Taps and Iterators

1-2 min read

March 26, 2021

When was the last time you saw someone drawing water out of a well? Depending upon your age, the answer could be never. Last time I saw a working well was years ago. I barely remember the incident, I had gone to a remote village and the people in that village were still using well to draw out water.

I have almost always used taps to get my water. And the answer is pretty obvious, to be honest. It’s much more elegant, safe, and allows us to focus on working with water rather than having to worry about how to get the water.

This is analogous to what iterators do in programming. It’s a completely different way to think about dealing with data. We no longer have to worry about how to get the data from a data structure.

Well, the above statement is not entirely true, we do have to think about it once. We have to set up the data structure to be processed by iterators just like we set up the pipeline for tap water. But once it’s done, it’s done.

Let’s take a simple example:

const team = {
  players: [
    {
      name: "Lionel Messi",
      position: "forward",
      jerseyNo: 10,
    },
    {
      name: "Sergio Busquets",
      position: "midfielder",
      jerseyNo: 5,
    },
    {
      name: "Frenkie de Jong",
      position: "midfielder",
      jerseyNo: 21,
    },
    {
      name: "Pedri",
      position: "midfielder",
      jerseyNo: 16,
    },
  ],
  coaches: [
    {
      name: "Ronald Koeman",
      type: "head",
    },
    {
      name: "Alfred Schreuder",
      type: "assistant",
    },
  ],
  doctors: [
    {
      name: "Ricard Pruna",
    },
  ],
};

Here we have a complex object describing a football team. Each team can have several different types of members. In our case, we have players, coaches, and doctors.

Now, let’s say a common operation throughout our program is to get the midfielders of our team. If we go with the imperative approach, the entire logic of ”checking if the team array is present or not, iterating over the team and checking until if the current player is a midfielder or not“, will be repeated in the code.

But if we do with iterators, we will only have to create an iterator once, and we can use it anywhere we want:

midfielderIterator = (team) => {
  if (!team) {
    throw new Error("Team must be present!");
  }
  if (team.players && !Array.isArray(team.players)) {
    throw new Error("players must be an array!");
  }
  const players = team.players || [];
  let currentPlayerIndex = 0;

  return {
    next: function () {
      while (
        currentPlayerIndex < players.length &&
        players[currentPlayerIndex].position !== "midfielder"
      ) {
        currentPlayerIndex++;
      }

      if (currentPlayerIndex >= players.length) {
        currentPlayerIndex = 0;

        return {
          value: undefined,
          done: true,
        };
      }

      const currentPlayer = players[currentPlayerIndex];
      currentPlayerIndex++;
      return {
        value: currentPlayer,
        done: false,
      };
    },
    [Symbol.iterator]: function () {
      return this;
    },
  };
};

Here’s how we can use it:

for (const midfielder of midfielderIterator(team)) {
  console.log(midfielder);
}

Output

{ name: 'Sergio Busquets', position: 'midfielder', jerseyNo: 5 }
{ name: 'Frenkie de Jong', position: 'midfielder', jerseyNo: 21 }
{ name: 'Pedri', position: 'midfielder', jerseyNo: 16 }

Hello 👋
Subscribe for wholesome stuff about life, tech, football, physics and everything else.
No spam, and there's unsubscribe link in every message.