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 expressions #46

Closed kosich closed 2 years ago

kosich commented 3 years ago

Hi, a great proposal, thanks for working on it!

I wanted to discuss the partial expressions option that is partially related to threads in #13, #20, and #23.

Suggestion: If we could extend ? syntax to be allowed inside any expression — we might improve readability in mapping, applying, and piping. It would support currently proposed syntax (with a nuance in nested calls) as well as new applications. The idea is that ? would bubble to the top of an expression, making it a function with one or many parameters and with that expression as returned value (no eager evaluation). And, I think this doesn't need any additional sigil prefix.

let a = ? + 42;
// to equal
let a = x => x + 42;

Please, see more examples and doubts review below


What do you think? Thanks 🙂

kosich commented 3 years ago

Examples:

Nothing changes in current syntax / behavior (with one level of call nesting, more on that later):

// partial application
let a = Math.max(?, 42);
let a = x => Math.max(x, 42);

// string templates
let b = `The answer is ${ ? }`;
let b = x => `The answer is ${ x }`;

As call context (related to @rbuckton suggestion in #23):

// NOTE: even ?. should not conflict in syntax w/ optional chaining, though might be confusing w/o parenthesis
let b = (?).foo(42);
let b = x => x.foo(42);

// in piping
42 |> ?.toString();
42 |> x => x.toString();

As a partial expression:

// simple case
let a = ? + 42;
let a = x => x + 42;

// in mapping
[1,2,3].map(? + 42);
[1,2,3].map(x => x + 42);

// in piping
[1,2,3] ​|> ? + 42;
(x => x + 42) ([1,2,3]);

and a similar behavior to @trustedtomato suggestion in #20 :

let id = ?;
let id = x => x;

let add = ? + ?;
let add = (x1, x2) => x1 + x2;
kosich commented 3 years ago

Doubts:

I. Syntax

I think, this syntax won't conflict with neither elvis operator 'a? b : c, nor with optional chaininga?.b. But I might be missing something. Maybe in case of conflicts we could use other sigil, like#`.

Also, the borders of the expression might not be obvious enough. More on this in next doubt:

II. Nested expressions and the border line:

Should this expression

let point = z => y => z + y + ?;

equal to point = x => z => y => z + y + x or to point = z => y => x => z + y + x? I'd prefer latter: function body is a borderline for a partial expression. But not sure if this behavior will be easy to define and (importantly) understand.

And it get's less obvious with nested fn calls:

let value = foo(bar(?, 42));

should it equal to value = foo(x => bar(x, 42)); or value = x => foo(bar(x, 42));? When talking about partial expression, latter makes more sense for me, as it raises to the top of the expression. Though it contradicts current proposal in nested partial application.

III. Curried partial expressions:

There are discussions around curried syntax for functions, so another question is should there be a syntax for curried partial expression and how to define it, so that ? + ? would generate x1 => x2 => x1 + x2, instead of (x1, x2) => x1 + x2.

IV. Extending this proposal might slow it down:

This is a huge change, and I don't want to slow the current proposal down. A separate proposal with different syntax for such partial expressions can be initiated (since there's probably a conflict in nested fn calls). And @trustedtomato has created a sigil-based one, though it seems to be stalled atm: https://github.com/trustedtomato/proposal-partial-expression.

If these "partial expressions" are worth exploring, should we limit the current proposal to partially applied functions only and restrict template strings from allowing ? for now? As it might conflict with partial expressions.

dead-claudia commented 3 years ago

I like the idea, but id = ? conflicts with your toString = (?).toString() - you should pick one and stick with it, or reject it altogether.

You should also be sure to constrain ExpressionStatement to include a [lookahead ∉ `?`] (similar to what it already does to avoid a conflict between block statements and object literals) to ensure it doesn't conflict with optional chaining.

let foo = i
?.foo()

I do have a suggestion: look into the possibility of using a separate grammar for partial terms, one that mostly aligns with expressions but deliberately doesn't satisfy the RHS grammar (as it doesn't evaluate to a value), and switching relevant constructs where useful to use that instead. (You could also make much better tradeoffs like potentially requiring parentheses for certain things like binary operations, and you could even look into things like operator functions like value.reduce(+, 0) rather than relying on purely ? to make everything work.)

pepkin88 commented 2 years ago

(You could also make much better tradeoffs like potentially requiring parentheses for certain things like binary operations, and you could even look into things like operator functions like value.reduce(+, 0) rather than relying on purely ? to make everything work.)

This looks like what LiveScript has with its operators: https://livescript.net/#operators-partial So in LiveScript you can turn an operator to a (partially applied) function, e.g.: + -> (+) or (+ 1). Basically any unary or binary operator have this feature. It works also for property access: (.a.b = c) would be the same as JS _ => _.a.b = c; (obj.) is like _ => obj[_]. There is also a typical partial application, restricted only to function calls: obj.method(?, 1, ?) (they have _ as the placeholder though).

I think it would look good in JS. It doesn't introduce another placeholder symbol, in my opinion would be easily understood: if an operator lacks its operand, then it's a function awaiting that operand. Personally, I'd love to see something like this in JS, although I'm not that hopeful.

ducaale commented 2 years ago

This looks like what LiveScript has with its operators: https://livescript.net/#operators-partial So in LiveScript you can turn an operator to a (partially applied) function, e.g.: + -> (+) or (+ 1).

@pepkin88 although not as versatile as livescript, I think the Functional operator propsal would fill that role.

rbuckton commented 2 years ago

Partial expressions require lazy evaluation (similar to an arrow function), while partial application uses eager evaluation (like f.bind()). I believe arrow functions are succinct enough for lazy evaluation, and have no plans to support arbitrary expressions for partial application.

I have been investigating functional operators similar to LiveScript's partial operators, but that will likely come as a later proposal.