Given that these function declarations exist within scope:
function compose() {
var funcs = arguments;
return function() {
var args, i;
args = arguments;
for (i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(null, args)];
}
return args[0];
};
}
function id(x) {
return x;
}
function partial(targetfn) {
var arity = targetfn.length;
return function fn() {
if (arguments.length < arity) {
return Function.prototype.bind.apply(fn, [null].concat(
Array.prototype.slice.call(arguments)
));
} else {
return targetfn.apply(null, arguments);
}
}
}
Having this callback function as an example:
function cb(x, y) {
console.log(x);
console.log(y);
}
Is it possible using FP techniques (such as argument binding, composition and partial application) to apply the callback function to 2 arrays of arbitrary length? E.g.: ["foo", "bar", "baz", "quux"] and [12, 42]? The expected output in this case would be:
foo
12
foo
42
bar
12
bar
42
baz
12
...and so on
Since Array.prototype.forEach and Array.prototype.map pass 3 arguments to the callback function, it's at least possible to iterate over a single array using function composition as follows:
["foo", "bar", "baz", "quux"].forEach(compose(cb, id))
It's also possible to map an array into partially applied functions like so:
[12, 42].map(compose(partial(cb), id))
I've been looking into using Function.prototype.apply, Function.prototype.bind and Function.prototype.call, combining them with Array.prototype.forEach and Array.prototype.map, so far without success. I'm trying hard not to introduce more function declarations / expressions.
Thanks to Mulan, the problem can be solved by splitting it into the part that computes the cartesian product of lists and applying the resulting list to a given function.
function product() {
var args = [].slice.call(arguments);
return args.reduce(function(prev, cur) {
var ret = [];
prev.forEach(function(x) {
cur.forEach(function(y) {
ret.push(x.concat(y));
});
});
return ret;
}, args.shift().map(function(val) {
return [val];
}));
}
product(
["foo", "bar", "baz", "quux"],
[12, 42]
).forEach(Function.prototype.apply.bind(cb, null));
I think you might be making more work for yourself by forcing compose and partial where they're not particularly useful. It seems like you're looking to compute a product of input arrays. The result is an array of products, each of which contains a unique grouping of elements taken from the input arrays -
function product(a, ...more) {
if (more.length == 0)
return a.map(v => [v])
else
return a.flatMap(v => product(...more).map(p => [v, ...p]))
}
function cb(x,y) {
console.log(x, y)
}
product(["foo", "bar", "baz", "quux"], [12, 42])
.forEach(p => cb(...p))
.as-console-wrapper { min-height: 100%; top: 0; }
If you really want to use partial, you can accept a callback cb as the first argument, and then all arrays to multiply thereafter. Each step in product applies one argument to cb. The result is an array of products where each product is an array of partially applied functions, which can be executed by calling cb() -
function partial(f, ...x) {
return (...y) => f(...x, ...y)
}
function product(cb, a, ...more) {
if (more.length == 0)
return a.map(v => partial(cb, v))
else
return a.flatMap(v => product(partial(cb, v), ...more))
}
const cb = partial(console.log, "i would like")
const res = product(cb,
["๐", "๐ฎ", "๐ฅช"],
["๐", "๐ฅจ"],
["๐ฅค", "โ๏ธ"]
)
res.forEach(cb => cb())
.as-console-wrapper { min-height: 100%; top: 0; }