Search code examples
javascriptfunctional-programmingdefaultdefault-value

Reversing the order of default arguments in JavaScript


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?


Solution

  • 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.