1 Comment

Collection Operations on Lazy Sequences in TypeScript with itiriri

I’m currently working in TypeScript after spending a lot of time in F#, and I’ve been experimenting with adapting my favored functional approaches to the former.

Recently, an Exercism.io puzzle called for iterating over a grid. A year ago, I would have done this procedurally:

for i=0 to 10
    for j=0 to 10
        // do stuff here

More recently, I’ve favored breaking a problem into a series of steps with clearly stated (and type-checked!) intermediate states:


getPoints(w,h).forEach(p => /* do the work*/)

In F#, I like to implement methods like getPoints() with lazy sequences:


let getPoints w h =
    seq {
        for i in [0..w] do
            for j in [0..h] do
                yield (i,j)
    }

Java(Type)Script has this too, in the form of generator functions. This seemed like a good opportunity to try them:


function* getPoints(w: number, h: number) {
  for (var i = 0; i < w; i++) {
    for (var j = 0; j < h; j++) {
      console.log(`generating [${i},${j}]..`);
      yield [i, j];
    }
  }
}

...and we have a sequence of points! Great! Now let's map over them, and...

Imagine my disappointment. For some reason, ES6 iterators don't support common collection methods like map, reduce, filter, etc.

How to Proceed

If you don't actually need the laziness, you can quickly turn the sequence into an array with spread syntax:


[...getPoints()].map(x => /* (...) */

This would be fine for my programming puzzle, but I'm a restless idealist, and I want something better.

I briefly looked into whether it'd be possible to hack map() onto iterables via prototype extension, but then I remembered how much I dislike working with prototypes and quickly turned back.

I checked with Lodash, and while the functional JavaScript library has some lazy evaluation capabilities of its own, it doesn't generally support ES6 iterators.

Finally, deep in a comment thread somewhere, I found the answer.

itiriri

itiriri is a functional utility library like Lodash, but built for ES6 iterators. And it's written in TypeScript, so its type definitions are unlikely to lie to you!

Finally, I can functionally operate on my sequence:


import { query } from "itiriri";

query(getPoints(10, 10))
  .filter(p => (p[0] + p[1]) % 3 == 0)
  .map(p => `(${p[0]},${p[1]})`)
  .take(5)
  .forEach(s => console.log(s));

Though it's not needed here, it's really cool that this runs lazily. You can see that the generation work is interspersed amongst the consumption work:

generating [0,7]..
generating [0,8]..
generating [0,9]..
(0,9)
generating [1,0]..
generating [1,1]..
generating [1,2]..
(1,2)

I should note that this isn't just for generator functions: JavaScript's data structures are iterable, too, so itiriri will happily operate on them!

Conclusion

JavaScript's map and filter have been around since ECMAScript 5.1 in 2011. Generators and iterators appeared in 2015 with the for..of loop to traverse them. Hopefully, the cross product of these—functional collection operations on lazy sequences—will come out of the box in some future language version.

Until then, I'm glad to have found itiriri.

Further Reading: