Closed gilbert closed 3 years ago
See also #75 and #84 for prior discussion on standardizing both styles’ operators.
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.
@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")?
@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.
@gilbert thanks, I'm catching up... The master
branch still has |> await f
. I see #85/#83 now.
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.
@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.
I like the Smart Mix option as well. I want to suggest a specific version of it, inspired by comments from @rbuckton:
?
as the placeholder, matching partial applicationawait
patch #85 ; no special behavior for yield
|> .method()
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?
@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.
@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.
@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.
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.
@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.
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.
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.
Why would x |> f(? + 1)
be a SyntaxError? That seems very confusing.
@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?
@gilbert restricting the usage to "once per pipeline entry" seems like it'd cover that?
@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.
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?
I don't think we want to pin special semantics to having anything wrapped in parenthesis.
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.
@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).
@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.)
Yep, partial application is a red herring with respect to pipelines.
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.
@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?
@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. 👍🏻
@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.
@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)?
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.
…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.
@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?
@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:
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.
@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:
- Smart-mix pipeline is a no-go
- 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.
@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.
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.
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.
@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?
@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.
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.
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.
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
@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.
@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.
@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.
Later, TC39 standardizes partial application, with a different placeholder of ▲
and no prefix. value |> pickEveryN(▲, 2)
and value |> pickEveryN(■, 2)
would remain equivalent.
value |> pickEveryN(▲, 2)
has no ■
in its RHS. It is therefore a F-sharp-style tacit call equivalent to pickEveryN(▲, 2)(value)
. That in turn is equivalent to pickEveryN(value, 2)
.
value |> pickEveryN(■, 2)
has a ■
in its RHS. It is therefore a Hack-style pipe equivalent to pickEveryN(value, 2)
.
Alternatively, let’s say that TC39 instead later standardizes partial application with a different placeholder of ▲
and a prefix of ::
. value |> ::pickEveryN(▲, 2)
and value |> pickEveryN(■, 2)
would remain equivalent.
value |> ::pickEveryN(▲, 2)
has no ■
in its RHS. It is therefore a F-sharp-style tacit call equivalent to ::pickEveryN(▲, 2)(value)
. That in turn is equivalent to pickEveryN(value, 2)
.
value |> pickEveryN(■, 2)
has a ■
in its RHS. It is therefore a Hack-style pipe equivalent to pickEveryN(value, 2)
.
Alternatively, TC39 standardizes partial application with an identical placeholder of ■
and a prefix of ::
. This is the idea in @gilbert’s https://github.com/tc39/proposal-pipeline-operator/issues/89#issuecomment-363561996. Here, value |> ::pickEveryN(■, 2)
and value |> pickEveryN(■, 2)
would also remain equivalent.
value |> ::pickEveryN(■, 2)
has no pipeline ■
in its RHS, because the only ■
is a partial-application placeholder within a ::
-delimited partial application. This pipe is therefore a F-sharp-style tacit call equivalent to ::pickEveryN(■, 2)(value)
. That in turn is still equivalent to pickEveryN(value, 2)
.
value |> pickEveryN(■, 2)
has a ■
in its RHS. It is therefore a Hack-style pipe equivalent to pickEveryN(value, 2)
.
Lastly, TC39 might standardize partial application with an identical placeholder of ■
and no prefix. This case alone is different, because in value |> pickEveryN(■, 2)
, the ■
would always mean a pipeline placeholder and never a partial-application placeholder. But, in the end, this pipe expression is still pickEveryN(value, 2)
, just like in the previous three hypothetical cases, whether or not the previous cases’ expressions involved partial-application syntax.
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.
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.
@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”.
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.
@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.
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.