tc39 / proposal-pipeline-operator

A proposal for adding a useful pipe operator to JavaScript.
http://tc39.github.io/proposal-pipeline-operator/
BSD 3-Clause "New" or "Revised" License
7.55k stars 108 forks source link

Discussion: Proposal Mixes #89

Closed gilbert closed 3 years ago

gilbert commented 6 years ago

Hi all, I've just updated the wiki with the current status of all proposals so far. This thread specifically is for exploring the possibility of mixing together the two main proposals (F-sharp style and Hack style). Please visit and read that page for details.

js-choi commented 6 years ago

See also #75 and #84 for prior discussion on standardizing both styles’ operators.

littledan commented 6 years ago

Great explanation there! The only other thing I'd like to see is a summary about how these are or aren't "forward-compatible" with @rbuckton's partial application operator, and what implications they'd have for the operator if compatible. It's OK if we decide that that's not a goal, but I'd like to be explicit about the tradeoff.

pygy commented 6 years ago

@gilbert thanks for the summary.

I'm surprised by the presence of await on its own line, I thought that |> await was meant to act as a composite binary operator of sorts. If I understand the current proposal, they could be moved to the line above without harm in all samples:

  |> (a => readDB(a, config))
  |> await

becomes

  |> await (a => readDB(a, config))

etc...

Do you mind if I add the https://github.com/tc39/proposal-pipeline-operator/issues/23#issuecomment-362386783 precedence proposal as a fifth option ("F# with lower precedence")?

gilbert commented 6 years ago

@littledan Sure, I added some bullets indicating compatibility.

@pygy For F-sharp style, |> await f was not chosen for two reasons: it behaves differently than |> (await f), and it presents an ASI hazard. Requiring it on its own line solves both these problems.

For the grammar point I think we should wait for an approval on its possibility from a tc member first. If approved, it would impact F-sharp usage sections instead of being a 5th option.

pygy commented 6 years ago

@gilbert thanks, I'm catching up... The master branch still has |> await f. I see #85/#83 now.

js-choi commented 6 years ago

Does this issue cover both the dual-operator Proposal 3: Split Mix and the single-operator Proposal 4: Smart Mix?

Because as far as I can tell, #75 already covers Proposal 4. The original post proposes a single pipeline operator that supports both F#-style tacit calling and Hack-style placeholder expressions.

mAAdhaTTah commented 6 years ago

@js-choi The conversation in #75 has bounced around quite a bit. With some concrete proposals to work off of, I think we should pick up both "mix" options here and close #75.


I really like "Smart Mix" with some bikeshedding over the token. Seems like a solid best of both worlds. Are dropping the closing-over behavior discussed elsewhere? I think it's simpler without it, but just want to make sure that wasn't overlooked.

littledan commented 6 years ago

I like the Smart Mix option as well. I want to suggest a specific version of it, inspired by comments from @rbuckton:

This version would be cleanly forward-compatible with the partial application proposal as it currently is. IMO unless we want to argue that partial application isn't going to happen, we should try to make placeholders explainable as them. Of course it's fair game to propose changes to partial application, but I don't really see any changes as being necessary here with the above options.

@rbuckton explained to me that, even with the partial application proposal landed, it still may make sense to support a direct semantics for pipeline placeholders, to make it so that the anonymous partially applied function unobservable (e.g., through engine-specific exception handling APIs which sometimes expose sloppy mode function identities).

What do you think?

gilbert commented 6 years ago

@littledan What you're suggesting is not a mix, but F-sharp behavior with partial application built-in. The hack-style behavior is gone, thus the need for |> .method(). Not saying yay or nay, just wanted to make that clear.

mAAdhaTTah commented 6 years ago

@littledan Does this imply that arbitrary expressions, e.g. |> 'Hello' + ? are possible on RHS? That was also one of the defining features of the Hack-style proposal.

littledan commented 6 years ago

@gilbert Yes, I see that it's different. Should we put it as a fifth option in the wiki?

@mAAdhaTTah No, this would be disallowed.

zenparsing commented 6 years ago

IMO unless we want to argue that partial application isn't going to happen, we should try to make placeholders explainable as them.

Then I think we ought to have that conversation, because the forward-compatibility constraint is leading towards a bizarre set of special-case rules that I don't think anyone wants or needs.

gilbert commented 6 years ago

@littledan Yes, I think it would go well as "Proposal 1b", since it's F-sharp plus some features.

To be honest I think it might be the one that works for everyone. At least, for what I think you're talking about:

// Basic Usage
x |> f         //-->  f(x)
x |> f(?)      //-->  f(x)
x |> f(? + 1)  //-->  SyntaxError
x |> f(y)      //-->  f(y)(x)

// 2+ Arity Usage
x |> f(?,10)     //-->  f(x,10)
x |> f(?,10, ?)  //-->  SyntaxError?

// Async Solution
x |> f |> await       //-->  await f(x)
x |> f |> await |> g  //-->  g(await f(x))

//
// Arbitrary Expressions Option 1 (nothing special)
//
f(x) |> (a => a.data)           //-->  f(x).data
f(x) |> (a => a['my-data'])     //-->  f(x)['my-data']
f(x) |> (a => ({ result: a }))  //-->  { result: f(x) }

//
// Arbitrary Expressions Option 2 (special casing)
//
f(x) |> .data                   //-->  f(x).data
f(x) |> ['my-data']             //-->  f(x)['my-data']
f(x) |> (a => ({ result: a }))  //-->  { result: f(x) }

//
// Arbitrary Expressions Option 3 (generic placeholder)
//
f(x) |> (?).data           //-->  f(x).data
f(x) |> (?)['my-data']     //-->  f(x).data
f(x) |> { result: (?) }    //-->  { result: f(x) }

Restricting the placeholder to function argument positions is actually useful for more than just "forward-compatibility with partial app" – it allows ? to be the token without issue, since it can no longer be mixed within an expression. In other words, no more confusion with optional chaining or nullish coalescing.

But it still would be compatible with a partial app proposal that might look something like this:

var f   = ::g(?, 10)    // apply 10
var log = ::console.log // method extraction

Not that it needs to be compatible technically, but it should be compatible semantically.

littledan commented 6 years ago

To be honest I think it might be the one that works for everyone.

This was my hope. The only downside, as @zenparsing points out, is unmotivated complexity if we don't have partial application.

Arbitrary Expressions Option 3 (generic placeholder)

I really like this option! It bridges the gap to get full expressiveness, without special cases. We can tell a simple story, that (?) is only available after |> since otherwise the scope is unclear. It's a Syntax Error if you get it wrong, anyway.

js-choi commented 6 years ago

As @gilbert and @mAAdhaTTah point out, this appears to be not a mix proposal but rather a variation of Proposal 1: F-sharp Style Only, with no opt-in Hack Style, because it forbids arbitrary expressions…

…except for the third option in https://github.com/tc39/proposal-pipeline-operator/issues/89#issuecomment-363261820, which appears to be the usual Proposal 4: Smart Mix but using a (?) nullary operator as its placeholder.

If that analysis is correct, I will add (?) to #91 and the wiki. But then would not x |> await (?) be the same as the x |> await special case? And x |> yield (?) would be valid too, right? What then would be the point of the await special case?

As for the first two options in https://github.com/tc39/proposal-pipeline-operator/issues/89#issuecomment-363261820, should they get their own issue, as a “Proposal 1b”? I can also add them to the wiki—though I am a priori not a fan of them, since they involve much syntactic special casing while losing much expressiveness.

It should be noted that option 3 can coexist with a ?-using partial-application syntax because it uses a different placeholder: it uses (?), not ?. This is similar to the <?> and {?} previously proposed by @ljharb in https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-362946212. But even ? itself can be used in Proposal 2 and 4 if syntactic-partial application is forbidden in Hack pipes’ RHSes (see https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-362982143).

@littledan: IMO unless we want to argue that partial application isn't going to happen, we should try to make placeholders explainable as them.

@zenparsing: Then I think we ought to have that conversation, because the forward-compatibility constraint is leading towards a bizarre set of special-case rules that I don't think anyone wants or needs.

This question—to what extent the placeholder in Proposals 2, 3, and 4 should resemble syntactic partial application’s ? placeholder—is probably already covered by the bikeshedding in #91. As for placeholders in F-sharp Style Only, perhaps that would be covered by that new issue for Proposal 1b.

ljharb commented 6 years ago

Why would x |> f(? + 1) be a SyntaxError? That seems very confusing.

gilbert commented 6 years ago

@ljharb Because ? is a full argument slot, rather than a variable with a value. This restriction prevents stuff like x |> f(? ?? ? ? : ?).

@js-choi You're right, if (?) were adopted, then there would be no need for the |> await special case. As you also mention, F-sharp plus (?) is the smart mix proposal. I suppose that might look something like this:

// Basic Usage
x |> f           //-->  f(x)
x |> f((?))      //-->  f(x)
x |> f(?)        //-->  Syntactic sugar?
x |> f((?) + 1)  //-->  f(x + 1)
x |> f(y)        //-->  f(y)(x)

// 2+ Arity Usage
x |> f((?),10)      //-->  f(x,10)
x |> f(?,10)        //-->  Syntactic sugar?
x |> f((?),10,(?))  //-->  Syntax error?

// Async Solution
x |> f |> await (?)       //-->  await f(x)
x |> await f((?))           //-->  await f(x)
x |> f |> await (?) |> g  //-->  g(await f(x))

// Arbitrary Expressions
f(x) |> (?).data          //-->  f(x).data
f(x) |> (?)['my-data']    //-->  f(x).data
f(x) |> { result: (?) }   //-->  { result: f(x) }

This seems pretty acceptable, aside from the requirement to write extra parens in e.g. x |> f((?), 10). But maybe that's ok?

ljharb commented 6 years ago

@gilbert restricting the usage to "once per pipeline entry" seems like it'd cover that?

js-choi commented 6 years ago

@gilbert: I must be misunderstanding something about this (?)-placeholder Smart Mix proposal. If bare ? is eventually used by syntactic partial application, then why would x |> f(?) not be equivalent to f(?)(x), f(x), and x |> f((?))? Ditto for x |> f(?, 10), which, as far as I can tell, would be equivalent to f(?, 10)(x) and f(x, 10) and x |> f((?), 10). And why would x |> f((?), 10, (?)) be a syntax error; why would it not be f(x, 10, x)? Thanks for your patience.

mAAdhaTTah commented 6 years ago

As for the first two options in #89 (comment), should they get their own issue, as a “Proposal 1b”?

I think it's close enough to being a type of "mix" that we can discuss it here.


Then I think we ought to have that conversation, because the forward-compatibility constraint is leading towards a bizarre set of special-case rules that I don't think anyone wants or needs.

If we want to keep open partial application's options for its choice of syntax as open as possible, that makes a decent case for F#-style, no placeholders, and defer placeholders to that proposal. I think this becomes more appealing if this syntactic change makes it feasible to write arrow functions without parens.

Otherwise, I think we should specify a goal in relation to the placeholders proposal before we bikeshed the token & mixes. If we use (?), what's the relationship between the placeholders proposals & how pipeline placeholders? Are they usable together, or are we expecting the placeholders proposal to supersede this?

zenparsing commented 6 years ago

I don't think we want to pin special semantics to having anything wrapped in parenthesis.

js-choi commented 6 years ago

Forward compatibility with the ?-using partial-application syntax is not even a problem if the Hack-pipe placeholder is not also ?. Forward compatibility is a problem only insofar that the Hack-pipe placeholder must also be ? (or something resembling it, like (?))…and I am not sure that this resemblance is a very important goal. Its placeholder and Hack pipes’ placeholders serve two quite different purposes. In any case, the Hack-pipe placeholder’s name is just more bikeshedding for #91.

mAAdhaTTah commented 6 years ago

@js-choi The concern is we could end up with two different placeholder syntaxes, one within pipelines and one outside of them. @littledan suggested "we should try to make placeholders explainable as them." I'm hesitant to include a placeholder syntax at all because of this, but if we're going to, I do think we need to explicitly decide the relationship between the placeholder we're trying to ship with the pipeline and the placeholder proposal, even if that is to agree that we're willing to potentially ship 2 placeholder syntaxes (inside & outside of pipelines).

js-choi commented 6 years ago

@mAAdhaTTah: Making pipe placeholders explainable as partial-application placeholders is an understandable goal, but I don’t see how it’s possible at a fundamental level, as long as we’re talking about Hack-style pipes that support arbitrary expressions. Partial application cannot explain anything other than function calls (and method calls if the partial-application syntax will support them). It alone cannot explain arbitrary expressions such as arithmetic operators, instanceof, typeof, await, yield/yield *, etc. Even if they had been coupled in their initial proposals, these two things are quite different beasts.

After all, this still is a valid use case: to use partial application inside of a Hack pipe, such as … |> f.bind(null, ^^, 10) |> … or … |> f(^^, 10, ?) |> ….

(This is true even with tc39/proposal-partial-application#13; even if syntactic partial application adopted a prefix as in □ foo(?), then either ? must not be shared between syntactic partial application and pipelining or syntactic partial application must be forbidden in each pipe’s RHS—because, e.g., … |> □ f(?, 10, ?) would be ambiguous.)

zenparsing commented 6 years ago

Yep, partial application is a red herring with respect to pipelines.

mAAdhaTTah commented 6 years ago

Making pipe placeholders “explainable as” partial-application placeholders is an understandable goal, but I don’t see how it’s possible at a fundamental level, as long as we’re talking about Hack-style pipes that support arbitrary expressions.

I agree, but I see this as an argument for F#-style rather than dropping the forward-compatibility requirement. Perhaps something like |> f(^^, 10, ?) contains a lot of power but I fear that pushes over too far into the "ASCII soup" concern voiced in https://github.com/tc39/proposal-pipeline-operator/issues/75#issuecomment-362411230.

Which is not to suggest I'm opposed to the "Smart Mix", although I might reframe is as "Hack-style with inlinable unary function", which is to say x |> fnA |> await $ |> fnB($, 2) is valid, using whatever placeholder we decide to use. What I am suggesting is that we don't cripple the Hack-style proposal for the sake of forward compatibility if it's not feasible to achieve with the full power Hack-style provides, and explicitly decide that's the route we're going to take.

littledan commented 6 years ago

@zenparsing

Yep, partial application is a red herring with respect to pipelines.

Could you explain what you mean here? Do you think partial application as @rbuckton proposed is a bad idea, or that it would be fine to have two separate tokens here, as they are doing different things?

js-choi commented 6 years ago

@mAAdhaTTah: I see this as an argument for F#-style rather than dropping the forward-compatibility requirement.

If that ambiguity (when making the partial-apply placeholder and pipe placeholder both ?) is an argument for Proposal 1: F-sharp Style Only…then that argument is strong only insofar that “making pipe placeholders explainable as partial-application placeholders” is a strong requirement. But is this requirement really such a desideratum? Non-function-call expressions such as method calls, arithmetic operators, instanceof, typeof, await, yield/yield *, etc.—would “making pipe placeholders explainable as partial-application placeholders” be worth making all of these other types of expressions into either ad-hoc special cases or simply not supporting them in pipelines at all?

Either way, the forward-compatibility requirement is moot; it’s covered by all four proposals. For Proposals 2, 3, and 4 it merely means that pipe placeholders can’t be the same as partial-apply placeholders.

And it seems to me that these two types of placeholders are fundamentally different concepts. Just look at f(?, 3, ?). This is a function with two parameters; when the function is called, the two ?s here may represent two different values. This is not at all similar to x |> f(^^, 3, ^^); there is but one parameter here with one value. Trying to explain pipelines (which are fundamentally unary) in terms of partial application (which is n-ary) seems to me to be a fundamental mismatch in their models.

@mAAdhaTTah: Perhaps something like |> f(^^, 10, ?) contains a lot of power but I fear that pushes over too far into the "ASCII soup" concern voiced in https://github.com/tc39/proposal-pipeline-operator/issues/75#issuecomment-362411230.

This particular ASCII soup is a result of mixing ^^ and ?. It is a problem only insofar that creating functions through partial application in a pipeline is frequent. And creating functions through partial application in a pipeline probably wouldn’t be frequent, much less so than something like await or ^^ + 1. But it’s still occasionally useful, and I think forbidding it would be much weirder.

And, hey, even if creating functions through partial application in a pipeline were super frequent, minimizing its visual noise would just be more bikeshedding for #91. We could always go Unicode, hahaha…oh, boy.

Also, hey, thanks for engaging so cordially on this. Just wanted to say that. 👍🏻

zenparsing commented 6 years ago

@littledan I think @js-choi just expressed the idea better than I could have:

Trying to explain pipelines (which are fundamentally unary) in terms of partial application (which are n-ary) seems to me to be a fundamental mismatch in their models.

js-choi commented 6 years ago

@zenparsing: Thanks, gosh. To directly answer @littledan’s question, does this mean you think that syntactic partial application, as proposed by @rbuckton, may be a good idea—but that it would be fine to have two separate tokens for partial-apply placeholders and pipe placeholders (assuming proposals 2, 3, or 4)?

ljharb commented 6 years ago

For the record, if partial application (with a syntactic marker) could be used with or without a placeholder - allowing it to function as method extraction syntax - then i would find it very teachable and sensible for it to have a different token from a pipeline placeholder.

However, if partial application requires a placeholder, then “unary vs n-ary” to me doesn’t change that i think it should be a similar token.

mAAdhaTTah commented 6 years ago

…then that argument is strong only insofar that “making pipe placeholders explainable as partial-application placeholders” is a strong requirement. But is this requirement really such a desideratum?

Correct, and honestly, I don't know. My gut feeling is still that two different placeholder syntaxes in the language feels like a "wart", and that we'd be better off trying to solve placeholders in a separate proposal. Admittedly, your point that they're fundamentally different things is fair, so I don't hold that opinion strongly, although I do think the fact that they're similar but different things makes it harder to learn both.

Non-function-call expressions such as method calls, arithmetic operators, instanceof, typeof, await, yield/yield *, etc.—are all of these worth either making ad-hoc special cases for each or simply not supporting them in pipelines at all?

The "unary operators" we could support, the rest would require arrow functions. If the parse change I linked to above makes arrow functions without parens feasible, this doesn't feel like a significant burden. I'm not sure the power afforded by the full Hack-style proposal is worth is complexity of possibly ending up with two different placeholder syntaxes.

Also, hey, thanks for engaging so cordially on this. Just wanted to say that. 👍🏻

Same! I've been very impressed with this conversation overall.

js-choi commented 6 years ago

@ljharb: For the record, if [partial application requires a placeholder] then i would find it very teachable and sensible for it to have a different token from a pipeline placeholder

However, if partial application requires a placeholder, then “unary vs n-ary” to me doesn’t change that i think it should be a similar token.

I’m highlighting the “similar” here, because “similar” is quite different here than “identical”. In https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-362946212, you floated the ideas of <?> and {?} for pipe placeholders. Would these be sufficiently (visually) similar to partial application’s ? to satisfy teachability/sensibility for you?

zenparsing commented 6 years ago

@js-choi

So do you think that syntactic partial application as proposed by @rbuckton may be a good idea, but that it would be fine to have two separate tokens for pipes and partial apply?

Taking these two ideas:

When I combine those two ideas I arrive at the conclusion that either:

  1. Smart-mix pipeline is a no-go
  2. Partial application is a no-go

I would prefer not to take a stance on partial application. Personally, I have some doubts, but it would be better not to have to make a commitment either way.

Also, note that a "smart mix" is going to require a commitment to partial application, because the design choices are not explainable without it.

js-choi commented 6 years ago

@zenparsing: I'm sympathetic with arguments suggesting that a "smart mix" would be confusing if partial application used a different token from a placeholder.

…I arrive at the conclusion that either:

  1. Smart-mix pipeline is a no-go
  2. Partial application is a no-go

I’m not sure why Proposal 4: Smart Mix in particular—rather than all proposals that involve Hack pipe placeholders (2, 3, and 4)—is called out here as mutually exclusive with ?-using syntactic partial application. The only thing that distinguishes Proposal 4 from 2 and 3 is that it supports tacit function calling; this is orthogonal to syntactic partial application. As long as syntactic partial application is possible in the RHS of any Hack-style pipe, then visual confusion between pipe placeholders and partial-application placeholders may occur. This applies to proposals 2 and 3 as well, not just 4.

So, the choice would actually be between proposal 2/3/4 and syntactic partial application. Except that there is a third choice: both proposal 2/3/4 and syntactic partial application with a carefully bikeshedded pair of placeholders for pipes and for partial application.

I think #91 should be exhausted for possible pairs of placeholders (for both Hack pipes and partial application) before we assume that Hack pipes and partial application are mutually exclusive. For instance, ? and <?> may be a visually acceptable pair; see https://github.com/tc39/proposal-pipeline-operator/issues/89#issuecomment-363486154.

And the juxtaposition of both pipe placeholders and partial-application placeholders in the same code may be infrequent anyway.

@zenparsing: Also, note that a "smart mix" is going to require a commitment to partial application, because the design choices are not explainable without it.

As far as I can tell, Proposals 2/3/4 would require a commitment to syntactic partial application only insofar that it cannot use ? as a placeholder due to its reservation by partial application.

…I’ve sure been using “only insofar” a lot.

ljharb commented 6 years ago

@js-choi I think the challenge here is that neither proposal imo can really proceed unless the interactions between the two are worked out.

Also, note that a "smart mix" is going to require a commitment to partial application, because the design choices are not explainable without it.

I agree.

zenparsing commented 6 years ago

As long as syntactic partial application is possible in the RHS of any Hack-style pipe, then visual confusion between pipe placeholders and partial-application placeholders may occur. This applies to proposals 2 and 3 as well, not just 4.

To an extent, yes, but I don't necessarily agree with the equivalences. In version 2 and 3, the operator itself (|> in the case of 2, |: in the case of 3) gives the user the necessary context to easily tell what is expected in the LHS.

We want to avoid the situation where the user is not sure which token to use in which context, and where they have to "consult the manual".

Do I do this:

value
  |> pickEveryN(^^, 2)

or this?

value
  |> pickEveryN(?, 2)

I can't remember...

As far as I can tell, Proposals 2/3/4 would require a commitment to syntactic partial application only insofar that it cannot use ? as a placeholder due to its reservation by partial application.

Sorry, I'm getting confused about the different proposals. I meant that with a syntax preserving forward compatibility with partial application and using the same token, we are effectively committing to partial application down the road, because the design doesn't make sense without it.

mAAdhaTTah commented 6 years ago

Do I do this:

value
  |> pickEveryN(^^, 2)

or this?

value
  |> pickEveryN(?, 2)

Besides straight-up adopting the placeholder proposal into this proposal, I fear this is going to be an inevitable result of trying to adopt any kind of placeholder into pipeline. Another option would be to outright ban the future placeholder proposal from being used in pipelines at all, leaving the developer with just one choice in the pipeline, the one we adopt.

js-choi commented 6 years ago

@zenparsing: Do I do this:


value
  |> pickEveryN(^^, 2)

or this?

value
  |> pickEveryN(?, 2)

I think I see. So the worry particular to Pipeline Proposal 4 plus syntactic partial application is that x |> f(3, ?) and x |> f(3, ^^) would be equivalent. This is because the RHS of x |> f(3, ?) contains no pipeline placeholder, which would make x |> f(3, ?) equivalent to f(3, ?)(x). Compare this to x |> f(^^, ?), which would evaluate to the uncalled partially applied function f(x, ?). This equivalency does not apply to Pipeline Proposals 2 and 3.

This equivalency might be at least somewhat undesirable, but is this actually such a bad thing, compared to other costs (forbidding partial application in pipelines, or not supporting for non-function-call expressions or requiring ad-hoc syntaxes, etc.)?

After all, whether x |> f(3, ?) and x |> f(3, ^^), either way they’re actually the same. The writing programmer might be briefly faced with a paralysis of choice, in the same way that they might have to choose between x * x and x ** 2, but in the end it doesn’t matter anyway. Whatever they choose, they’d make no error here anyway, because they’re functionally the same anyway.

@mAAdhaTTah: Besides straight-up adopting the placeholder proposal into this proposal, I fear this is going to be an inevitable result of trying to adopt any kind of placeholder into pipeline. Another option would be to outright ban the future placeholder proposal from being used in pipelines at all, leaving the developer with just one choice in the pipeline, the one we adopt.

I’m a bit confused by the wording here. By “placeholder proposal” here, do you mean the proposal for syntactic partial application? Or do you mean Pipeline Proposals 2/3/4?

mAAdhaTTah commented 6 years ago

@js-choi I meant syntactic partial application. I was suggesting that a potential solution might be to tie pipelining and partial application into one proposal.

I also don't think the current await solution is "ad-hoc." I think it follows naturally from considering operators like functions.

zenparsing commented 6 years ago

I think it follows naturally from considering operators like functions.

Well, that's the whole issue. Operators (and JS expressions in general) are not functions, and attempting to make them behave like functions requires ad-hoc rules.

mAAdhaTTah commented 6 years ago

Fair enough. I do think these ad-hoc rules are easier to learn, because of that mental shortcut, but there is some complexity there for the language that I may not fully appreciate.

gilbert commented 6 years ago

Do I do this... or this (#89)

In my mind the partial application proposal has a prefix operator (like ::) which would help this situation even if both proposals use the same token:

value
  |> ::pickEveryN(^^, 2) // Partial app
value
  |> pickEveryN(^^, 2) // Not partial app
js-choi commented 6 years ago

@gilbert: Assuming that your example is using Proposal 4 with ^^ as a placeholder for both pipes and partial applications, then what precisely would value |> ::pickEveryN(^^, 2) mean here, if not value |> pickEveryN(^^, 2)?

Under the rules of Proposal 4, the pipe is a F-sharp-style tacit function call iff there is no pipe placeholder in the RHS; otherwise, it is an Hack-style arbitrary expression. If the ^^ in the RHS of value |> ::pickEveryN(^^, 2) is a partial-application placeholder, then that means the RHS of the pipe would contain no pipe placeholder. Would that not mean that value |> ::pickEveryN(^^, 2) pipe would be be a tacit call, equivalent to ::pickEveryN(^^, 2)(value)? If so, that would be equivalent to pickEveryN(value, 2), which would be equivalent to value |> pickEveryN(^^, 2) anyway.

I don’t see this as a problem with Proposal 4, but this is even more moot with Proposals 2 and 3, which should also be considered.

mAAdhaTTah commented 6 years ago

@js-choi That would be true only if we both explicitly choose the same token as partial application and commit to being forwards-compatibile with it.

js-choi commented 6 years ago

@mAAdhaTTah: My apologies if I’m being obtuse, but I still don’t yet understand. Why would this equivalency not be true if TC39 made no commitment to use the same token for the partial-application placeholder as the pipe placeholder?

Let’s say that TC39 standardizes smart pipes (Proposal 4) with an optional placeholder of . Let’s also say that they make no simultaneous commitment for partial application.

If this analysis is correct, then in all possible cases—no matter any TC39 commitment to standardizing syntactic partial application in the future, and whether or not that future partial-application syntax’s placeholders have the same token as the pipeline syntax’s placeholders—the smart pipe operator would not care whether a programmer uses partial-application syntax or not. It would be functionally indistinguishable either way (except for memory and garbage collection for any one-off partial-application functions).

All of the above applies to Proposal 4: Smart Mix. It also applies to Proposal 2 and 3, though, except that some of the cases’ expressions above would instead be syntax errors (if the answer to https://github.com/tc39/proposal-pipeline-operator/issues/84#issuecomment-362767598 is yes).

The s and s are arbitrary syntax characters for illustration only.

mAAdhaTTah commented 6 years ago

The examples above don't hold for multiple arguments, e.g.:

val |> fn(?, ?)
// with placeholders, this desugars to
fn(val, val)
// but partial app desugars to
fn(val)

Otherwise, that's basically right, but that's kind of what I'm saying: as long as the partial application proposal works the same way as our placeholder proposal, such that the equivalencies work the way you suggest, then it'll work; we'll be able to explain pipeline placeholders in terms of partial application. If something changes in the partial application proposal such that the single argument case is no longer compatible, then the equivalencies you identify no longer hold.

So the token part of what I said is incorrect (2 mistakes in one day! time go home...), but the forwards-compatibility part isn't.

That said, the result would be an overlap between the two features but in a venn-diagram sort of way, where only one placeholder + function call are the same. Multiple placeholders would function differently between pipelines + partial app, and arbitrary expressions are (obviously) not valid for partial app.

js-choi commented 6 years ago

@mAAdhaTTah: I see what you mean by fn(?, ?)(val). Yes, there’s that fundamental mismatch from https://github.com/tc39/proposal-pipeline-operator/issues/89#issuecomment-363479033, between pipeline placeholders and partial-application placeholders…

…Though I still don’t yet understand the forward-compatibility part. Could you clarify what is meant by “forward compatibility”?

I’m writing out all possible future interactions between pipes and partial application in a tabular format, but first I want to make sure that we’re talking about the same thing by “forward compatibility”.

mAAdhaTTah commented 6 years ago

Using your example, with ■ for pipelines placeholders and ▲ for partial app, forwards compatibility would be that a given function call with a single placeholder in a pipeline is equivalent regardless of the placeholder:

x |> fn(■, 2)
x |> fn(▲, 2)
// both are equivalent to:
fn(x, 2)

and I can thus swap the symbols with no change in observed behavior.

After writing that out though, that looks like a pretty narrow band where they both can behave the same.

js-choi commented 6 years ago

@mAAdhaTTah: I see! Thank you. That does seem like a narrow use case, but perhaps I am missing something. Assuming that example’s notation, then for what reason would a programmer ever want to rewrite x |> fn(■, 2) as x |> fn(▲, 2)? Is it to define fn(▲, 2) as a variable elsewhere then reuse it in pipes? If that’s the case, then I could see that…

…But defining fn(▲, ▲, 2) in a variable then using it in a pipe wouldn’t work anyway, though…even for Proposal 1: F-sharp Style Only, let alone the other three proposals. Unless the programmer actually means to have the result be a function with one parameter, which could happen, but not very frequently. There’s that fundamental impedance mismatch here: “Is it even meaningful to pipe a single unary value into an n-ary partial application, other than to return a (n − 1)-ary function?”

In any case, I’m going to start writing that table of possible future interactions now.