norbertpy norbertpy - 6 months ago 10
Javascript Question

Functional way for custom iteration

How can I use only

map
,
reduce
or
filter
or any functional way to create a custom iteration on an array?

Let's say I wanna map an array to another array which holds the sum of each three adjacent elements in the source array:

var source = [1, 2, 3, 4, 6, 7, 8] // to [6, 17, 8]


Or make a bucket of two elements:

var source = [1, 2, 3, 4, 5, 6, 7] // to [[1, 2], [3, 4], [5, 6], [7]]


For the second one I have the following but that doesn't look very functional as I'm accessing array by index:

function* pairMap(data) {
yield* data.map((item, index) => {
if (index > 0) {
return [data[index - 1], item];
}
});
}


I'm interested in the functional way of doing this.

Answer

Let's say I wanna map an array to another array which holds the sum of each three adjacent elements in the source array:

var source = [1, 2, 3, 4, 6, 7, 8] // to [6, 17, 8]

Maps create 1:1 relationships, so this wouldn't be appropriate use of map. Instead, a reduce or ("fold") would be better here.

const comp = f=> g=> x=> f (g (x));
const len = xs=> xs.length;
const isEmpty = xs=> len(xs) === 0;
const concat = xs=> ys=> ys.concat(xs);

const chunk= n=> xs=>
    isEmpty (xs)
        ? []
        : concat (chunk (n) (xs.slice(n))) ([xs.slice(0,n)]);

const reduce = f=> y=> xs=> xs.reduce((y,x)=> f(y)(x), y);
const map = f=> xs=> xs.map(x=> f(x));
const add = x=> y=> y + x;
const sum = reduce (add) (0);

var source = [1, 2, 3, 4, 6, 7, 8];
comp (map (sum)) (chunk (3)) (source);
//=> [ 6, 17, 8 ]

So as you can see, we first transform the source into chunks of 3, then we map the sum function over each chunk.

When you hear people talking about "declarative" code, the last line is quite clear and worries little about implementation. We're not telling the computer how to do its job. There's no for/while loops, no extraneous vars or iterators, no logic, etc.

"idgaf how, just break source up into groups of 3, and then sum each part"

// very declaration, wow
comp (map (sum)) (chunk (3)) (source);

Or make a bucket of two elements:

var source = [1, 2, 3, 4, 5, 6, 7] // to [[1, 2], [3, 4], [5, 6], [7]]

Using the same code above

var source = [1, 2, 3, 4, 5, 6, 7];
chunk (2) (source);
// => [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7 ] ]

For the second one I have the following but that doesn't look very functional as I'm accessing array by index:

function* pairMap(data) {
      yield* data.map((item, index) => {
         if (index > 0) {
              return [data[index - 1], item];
          }
      });
  }

Using the code above, you can implement pairMap easily

const pairMap = f=> comp (map (f)) (chunk (2));

var source = [1, 2, 3, 4, 5, 6, 7];
pairMap (pair => console.log(pair)) (source);
// [ 1, 2 ]
// [ 3, 4 ]
// [ 5, 6 ]
// [ 7 ]

Learn all the things

The question is "functional way for custom iteration". You'll notice my code sorta cheats by using Array.prototype.reduce and Array.prototype.map. Learning how to build these on your own was a good learning tool for me to understand that building functional loops/iterators/control is fun and simple

const isEmpty = xs=> xs.length === 0
const head = xs=> xs[0];
const tail = xs=> xs.slice(1);

const reduce = f=> y=> xs=>
  isEmpty (xs)
    ? y
    : reduce (f) (f (y) (head (xs))) (tail (xs));

const add = x=> y=> y + x;
reduce (add) (0) ([1,2,3]);
//=> 6

It works!.

OK, let's see how we'd do map

const concat = xs=> ys=> ys.concat(xs);
const append = x=> concat ([x]);

const map = f=>
  reduce (ys=> x=> append (f (x)) (ys)) ([]);

const sq = x => x * x;
map (sq) ([1,2,3])
//=> [ 1, 4, 9 ]

Quiz 1: Can you write filter, some, and every using reduce ?

Troll warning: There's many different ways to implement these functions. If you start writing recursive functions, the first thing you'll want to learn about is what a tail call is. ES6 is getting tail call optimization but it won't be widespread for a while. For a while, Babel could transpile it using a while loop, but it's temporarily disabled in version 6 and will be coming back once they fix it.

Quiz 2: How can you rewrite my reduce with a proper tail call?