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]