I’ve been reading about functional programming lately. It’s quite alluring, but it often seems impractical. Particularly the “no statements” thing. I had some time at work today, so I tried to write a generic functional utility to replace loops. Yes, all loops.
const loop = (
int = 0,
test = x => x < 0,
next = (x, y) => [...y, x],
iter = x => x + 1,
acc = [],
data = null
) => test(int, acc, data) ?
loop(
iter(int, acc, data),
test,
next,
iter,
next(int, acc, data),
data
) :
acc;
For a simple case using the default parameters, it’s actually kind of elegant.
//exhibit a
const arr = [];
for (let x = 0; x < 3; x++) {
arr.push(x);
}
arr; //[0, 1, 2]
//exhibit b
loop(0, x => x < 3); //[0, 1, 2]
It’s slightly less elegant, but still useful, for more interesting cases.
//exhibit b
const fizzBuzz = () => loop(
1,
x => x <= 100,
(x, y) => [
...y,
x % 3 === 0 && x % 5 === 0 ? "FizzBuzz" :
x % 3 === 0 ? "Fizz" :
x % 5 === 0 ? "Buzz" :
x
]
);
fizzBuzz(); //[1, 2, "Fizz"...]
//exhibit c
const ticTacToe = () => loop(
0,
x => x < 3,
(x, y) => [...y, loop(
0,
x => x < 3,
(x, y) => [...y, 0]
)]
);
ticTacToe(); /*[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]*/
Just for fun, because this is apparently what I do for fun, I decided to try something more complex. This is a utility function from my dungeon crawler game.
//exhibit e
const findRange = (yx, bounds = [2, 4, 6, 6, 7, 7, 8, 8, 8, 8, 8, 7, 7, 6, 6, 4, 2]) => {
//get radius around index
const range = [];
const ymax = (bounds.length - 1) / 2;
for (let i = -ymax; i < ymax + 1; i++) {
const xmax = bounds[i + ymax];
for (let j = -xmax; j < xmax + 1; j++) {
range.push([yx[0] + i, yx[1] + j]);
}
}
return range;
};
findRange([21, 42]); /*[
[13, 40],
[13, 41],
[13, 42]...
]*/
//exhibit f
const findRange = (yx, bounds = [2, 4, 6, 6, 7, 7, 8, 8, 8, 8, 8, 7, 7, 6, 6, 4, 2]) => loop(
//get radius around index
(-((bounds.length - 1) / 2)),
(x, y, z) => x < z.ymax + 1,
(x, y, z) => loop(
(-z.bounds[x + z.ymax]),
(x, y, z) => x < z.xmax + 1,
(x, y, z) => [...y, [z.yx[0] + z.x, z.yx[1] + x]],
void 0,
y,
Object.assign(z, {
x: x,
xmax: z.bounds[x + z.ymax]
})
),
void 0,
void 0,
({
yx,
bounds,
ymax: (bounds.length - 1) / 2
})
);
findRange([21, 42]); /*[
[13, 40],
[13, 41],
[13, 42]...
]*/
God, that’s awful. It’s also about 50 times slower than the original. It’s only 5 times slower using Array.prototype.push
instead of ...
, but that introduces mutation. I invite anyone reading this to take a stab at writing a better utility.
The rules:
- No libraries
- No statements (const doesn’t count)
- No mutation
- Pure functions only