tc39 / proposal-partial-application

Proposal to add partial application to ECMAScript
https://tc39.es/proposal-partial-application/
BSD 3-Clause "New" or "Revised" License
1.02k stars 25 forks source link

Partial application for “named” parameters? #7

Closed rauschma closed 3 years ago

rauschma commented 7 years ago

If named parameters are simulated via object literals and destructuring then partial application would be just as useful. For example:

function add({x,y}) {
    return x + y;
}
const plus2 = add({y:2, ...});
rbuckton commented 5 years ago

Unfortunately, while binding patterns in parameters are syntactically known to be declarations, object literals in arguments are just regular values. Supporting placeholders in an object literal as part of a call would have a significant number of ramifications with respect to the eager evaluation semantics for partial application.

Consider the following and its transposed representation:

// const g = add({ y: 2, x: ? });
const g = (() => {
  const fn = add;
  const p0 = "y";
  const p1 = 2;
  const p2 = "x";
  return (a0) => fn({ [p0]: p1, [p2]: a0 });
})();

You would have to completely unroll object literal allocation for every part of an expression that could be evaluated.

Also, your example adds even more complexity, given that it overloads the meaning of ... even more in partial application. I originally envisioned ... to mean "spread the rest of the arguments here":

// const g = f(...);
const g = (() => {
  const fn = f;
  return (...an) => fn(...an);
})();

This wouldn't work as well for the object literal case, since ... in an object literal means to spread the properties of an object, while ... in an argument list means to spread the elements. Spreading the properties of g({ x: 1 }) would make the result { y: 2, ...[{ x: 1 }] } (i.e. { y: 2, 0: { x: 1 } }). We'd probably need to include something like a ?... token to get the behavior you want.

This also complicates the proposal even further because we'd effectively be allowing ? in an arbitrary position in a call list. If we could do add({ y: 2, x: ? }), we couldn't we do f(? + 1). This arbitrary nesting seems fine until someone goes one step to far and writes f(Math.abs(?) + 1) and then the ? applies to the wrong argument.

rbuckton commented 3 years ago

The recent change to partial application syntax to use a unique invocation syntax (i.e., f~()) further makes clear the intention that placeholders are bound to an argument and not an arbitrary expression position. Supporting placeholders in object literals is currently out of scope for this proposal. However, it is something we might consider in a follow-on proposal should this advance to Stage 2.

One option might be for a future proposal to extend the syntax we recently added for positional placeholders (i.e., ?0, ?1, etc.) to support partial literals within a partial function:

function add({x,y}) {
    return x + y;
}

add~(?{ x: ?, y: ? });

Another option might be a proposal to introduce actual named parameters to ECMAScript, and propagate them in both Partial Application and Function.prototype.bind:

function add(x, y) { return x + y; }

// out-of-order arguments by name
add(y: 1, x: 2);

// partial application with named arguments
const plus2 = add~(y: 2, ?);

plus2(x: 1); // preserve argument name in partial result

const bound = add.bind(null, 1);
bound(y: 2); // preserve argument name in bind

// possible mechanisms to bind named arguments:
const bound2 = add.bind(null, Function.named({ y: 2 })); // special named argument exotic object?
const bound3 = add.bindNamed(null, { y: 2 }); // or just a new method on Function.prototype?

In the mean time, we would recommend that you use (or continue to use) arrow functions to handle scenarios where object literals are used as named arguments.