5
\$\begingroup\$

I'm trying to make a partial function from any function.

Here is a working example:

function partial(fn) { const argc = fn.length; const argv = []; return function part(...args) { argv.push(...args); return argv.length >= argc ? fn(...argv) : part; } } 

Usage:

 function foo(a, b, c, d) { return `foo: ${a}, ${b}, ${c}, ${d}`; } console.log( partial(foo)(1, 2, 3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1, 2)(3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1, 2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1)(2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)()(1, 2)()(3)()(4) ); // => foo: 1, 2, 3, 4 

However, I'm not completely happy with it because of argv.push(...args);.

Is there a better, shorter, or more canonical way to make a partial function application in JavaScript?

Edit: Replaced deprecated arguments with rest parameters...args.

\$\endgroup\$

    1 Answer 1

    6
    \$\begingroup\$

    An interesting question,

    I have never seen a partial functional application like that, I am used to see something more like

    function partial2(f, ...args) { return function() { return f.apply(this, args.concat(...arguments)); }; } console.log(partial2(foo, 1, 2)(3,4)); // => foo: 1, 2, 3, 4 

    Where you create once the partial application, and that's it. To me, that is the more canonical way.

    Your code is in a way more flexible and fun. The only thing I can provide is to note that Array.push returns the new count elements, and Array.length provides the current count of elements. This means you could just skip the request of argv.length and directly work with the return value of argv.push(...args)

    function partial3(f) { const argc = f.length, argv = []; return function partialFunction(...args) { return argv.push(...args) >= argc ? f(...argv) : partialFunction; }; } 

    And actually, the caching of argc seems a bit overkill, you only use f.length once.

    function partial3(f) { const argv = []; return function partialFunction(...args) { return argv.push(...args) >= f.length ? f(...argv) : partialFunction; }; } 

    Final thought, this might need some more fiddling.

    const myPartial = partial3(foo)(1, 2, 3); console.log( myPartial('a') ); console.log( myPartial('b') ); console.log( myPartial('c') ); 

    The expectation of a partial is that this should return

    foo: 1, 2, 3, a foo: 1, 2, 3, b foo: 1, 2, 3, c 

    but it returns

    foo: 1, 2, 3, a foo: 1, 2, 3, a foo: 1, 2, 3, a 

    because the partial function keeps too much state...

    Belows is a snippet with the different functions and some tests;

    function partial(fn) { const argc = fn.length; const argv = [] return function part(...args) { argv.push(...args); return argv.length >= argc ? fn(...argv) : part; } } function partial2(f, ...args) { return function() { return f.apply(this, args.concat(...arguments)); }; } function partial3(f) { const argc = f.length, argv = []; return function partialFunction(...args) { return argv.push(...args) >= argc ? f(...argv) : partialFunction; }; } function foo(a, b, c, d) { return `foo: ${a}, ${b}, ${c}, ${d}`; } console.log( partial(foo)(1, 2, 3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1, 2)(3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1, 2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)(1)(2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial(foo)()(1, 2)()(3)()(4) ); // => foo: 1, 2, 3, 4 console.log( partial2(foo, 1, 2)(3,4) ); // => foo: 1, 2, 3, 4 console.log( partial3(foo)(1, 2, 3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial3(foo)(1, 2)(3, 4) ); // => foo: 1, 2, 3, 4 console.log( partial3(foo)(1, 2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial3(foo)(1)(2)(3)(4) ); // => foo: 1, 2, 3, 4 console.log( partial3(foo)()(1, 2)()(3)()(4) ); // => foo: 1, 2, 3, 4 const myPartial = partial3(foo)(1, 2, 3); console.log( myPartial('a') ); console.log( myPartial('b') ); console.log( myPartial('c') );

    \$\endgroup\$
    2
    • \$\begingroup\$I see a meaningful usage of the return value of push for the first time :) return argv.push(...args) >= argc ? I always thought it should return a ref to the array itself.\$\endgroup\$CommentedOct 13, 2023 at 17:26
    • \$\begingroup\$@MiroslavPopov Fully agreed, if I wanted length, I could have called .length on the returned list.\$\endgroup\$
      – konijn
      CommentedOct 14, 2023 at 14:44

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.