I have the following recursive compose
function:
const compose = (f, n = 1) => n > 1 ?
compose(compose(f), n - 1) :
g => x => f(g(x));
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = compose(length, 2)(filter);
const odd = n => n % 2 === 1;
console.log(countWhere(odd)([1,2,3,4,5,6,7,8,9])); // 5
Now, what I'd like to do is flip the arguments of compose
so that the default argument is first:
const compose = (n = 1, f) => n > 1 ? // wishful thinking
compose(n - 1, compose(f)) : // compose(f) is the same as compose(1, f)
g => x => f(g(x));
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = compose(2, length)(filter); // I want to call it like this
const odd = n => n % 2 === 1;
console.log(countWhere(odd)([1,2,3,4,5,6,7,8,9])); // 5
What's the most elegant way to write such functions where the default arguments come first?
Edit: I actually want to create the map
and ap
methods of functions of various arities, so that I can write:
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = length.map(2, filter); // length <$> filter
const pair = x => y => [x, y];
const add = x => y => x + y;
const mul = x => y => x * y;
const addAndMul = pair.map(2, add).ap(2, mul); // (,) <$> (+) <*> (*)
Hence, I'd rather not curry the methods as Bergi suggested in his answer.
For more information, read: Is implicit wrapping and unwrapping of newtypes in Haskell a sound idea?
What's the most elegant way to write such functions where the default arguments come first?
Using only default initialisers requires some arcane hackery:
function demo(n, f = [n, n = 1][0]) {
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 1
The most straightforward way would be destructuring with a conditional operator:
function demo(...args) {
const [n, f] = args.length < 2 ? [1, ...args] : args;
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 0
More in the spirit of "reversing the order of arguments" might be doing that literally:
function demo(...args) {
const [f, n = 1] = args.reverse();
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 0
The latter two attempts have the drawback of requiring an extra declaration (preventing us from using concise arrow functions) and also don't reflect the actual number or required parameters in the .length
.