7

I recently heard about Facebook's Immutable.js library (https://github.com/facebook/immutable-js). I am confused about the following from their documentation:

var oddSquares = Immutable.Sequence(1,2,3,4,5,6,7,8)
  .filter(x => x % 2).map(x => x * x);
console.log(oddSquares.last());

In this example, no intermediate arrays are ever created, filter is only called twice, and map is only called once

How is filter called only twice, map once?

tldr
  • 11,924
  • 15
  • 75
  • 120

1 Answers1

14

Because of the logic of map and filter regarding lazy evaluation of sequences

map

last() called over a mapped sequence return the last() of the original sequence processed by the mapper function.

So for example:

 var mappedSequence = Immutable.Sequence(1,2,3,4,5,6,7,8).map(x => x * x);
 console.log(mappedSequence.last());

Will output 64 and will call map only once, because the only thing it does is get the last element of original sequence (8) and map it on x => x * x (resulting on 64)

filter

last() called over a filtered sequence, will reverse walk the sequence until it finds a value on sequnce matching the criteria. So, for example

 var mappedSequence = Immutable.Sequence(1,2,3,4,5,6,7,8).filter(x => x % 2);
 console.log(mappedSequence.last());

WIll output 7 and will call filter only twice, because it call the filter (x => x % 2) for 8 first, it returns 0 meaning false for javascript (so it should be filtered), and then call the filter function again to get 7 % 2 = 1 being true for javascript and returning that value as the last without calling filter functoin anymore.

As additional example to help understand:

 var mappedSequence = Immutable.Sequence(1,2,3,4,6,8).filter(x => x % 2);
 console.log(mappedSequence.last());

Would call the filter function four times, one for 8 (resulting false), one for 6 (false again), one for 4 (false again), and finally for 3 (finally resulting true)

Putting both pieces together

Your example:

var oddSquares = Immutable.Sequence(1,2,3,4,5,6,7,8)
   .filter(x => x % 2).map(x => x * x);
console.log(oddSquares.last());
  1. To get the last() value of mapped sequence, it first get the last() value of filtered sequence
  2. To get the last() value of filtered sequence, it first get the last() value of original sequence (8) and evaluate it agains the filter function ( x => x % 2 ), calling it for first time
  3. Since 8 % 2 = 0, it is false on JS and should be filtered, so, we move to the next value (7) and call the filter function again with this value
  4. 7 % 2 = 1, it is true on JS and should NOT be filtered, so, this is the last value of the filtered sequence
  5. We have the last value (7) required by the mapped sequence, so we call mapper function (x => x * x) only one time to get 49, the final result

At the end, we get called the filter function two times and the mapper function only once

dseminara
  • 11,665
  • 2
  • 20
  • 22
  • IMHO the wording in the documentation is subtly wrong. The functions you *supply* to the `filter` and `map` functions are only called twice and once, respectively. In this case lambdas but you can supply named functions as well. – wensveen Sep 08 '22 at 06:56
  • I am sure my english was pretty broken then and even now :( (a bit less broken), I am sure I made lot of mistakes. I will appreciate a well written answer. I don't have time to review this , sorry (I am just sure I made some gramatical/vocabulary mistake, that's for sure) – dseminara Sep 09 '22 at 20:16
  • Your English is fine, I was talking about the documentation. And it's a bit of a nitpick. There's a distinction between the filter/map functions and the functions that you supply to these functions as the argument (callbacks). So when you say 'mapper function' it's clear that you don't talk about the `map` function, but when you say 'filter function' there's ambiguity between the function named `filter` and the function you supplied as the argument. `seq.filter(ffn); // ffn = filtering function` `seq.map(mfn); // mfn = mapping function` – wensveen Sep 11 '22 at 07:12