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\$ Commented Oct 13, 2023 at 17:26
  • \$\begingroup\$ @MiroslavPopov Fully agreed, if I wanted length, I could have called .length on the returned list. \$\endgroup\$
    – konijn
    Commented Oct 14, 2023 at 14:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.