Skip to content

Many Maps

I briefly covered a mapping function in "The Functional Shape of Loops" which can be used for one-to-one mapping, meaning that one item in the input collection produces one value in the output collection:

[1, 2, 3] |> map((n) => n * 2); // [2, 4, 6]

Sometimes it's useful to have a one-to-many mapping, where one input produces multiple output values.

With the classic map function the output becomes a collection of collections:

[1, 2, 3] |> map((n) => [n, n * 2]); // [[1, 2], [2, 4], [3, 6]]

Sometimes lists of lists are useful, but quite often it's more helpful to have a flattened collection of nodes.

This can be handled with a simple reducer function:

[1, 2, 3]
  |> map((n) => [n, n * 2])
  |> reduce((acc, x) => acc.concat(x));
  // [1, 2, 2, 4, 3, 6]

However, given how common one-to-many mappings are, it would be useful to be able to express this transformation in a single function.

The original map function has an implementation of:

const map = (fn) =>
  function* (collection) {
    let i = 0;
    for (const value of collection) {
      yield fn(value, i++);
    }
  };

where yield returns a single value. If we instead use yield *, each collection returned by fn will automatically be concatenated into a single output collection:

const mapMany = (fn) =>
  function* (collection) {
    let i = 0;
    for (const value of collection) {
      yield* fn(value, i++);
    }
  };

Which allows our implementation of the previous example to turn into:

[1, 2, 3] |> mapMany((n) => [n, n * 2]); // [1, 2, 2, 4, 3, 6]