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?