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

Replacing `::` completely with pipelines #110

Closed cshaa closed 3 years ago

cshaa commented 6 years ago

I'm writing this issue in reaction to #107 and tc39/proposal-bind-operator#44 where there is a vivid discussion about the future of the :: operator and its relation to the proposed pipeline operators.

Let me start with saying that the :: operator proposal has been around for quite a long time and there have been some major advancements in the Pipeline Operator Proposal since then. For example the Smart Pipelines variant with Additional Feature PF is much cleaner and more universal than the unary binding variant of the :: operator can ever hope to get. I think that the ease with which +> console.log replaces ::console.log while offering much more functionality implies that the design of :: needs heavy rethinking.

Now let me dissect the three variants of ::, one at a time:

.

.

.

TL;DR

To sum up, we saw that the Smart Pipelines together with a light-weight pipeline-friendly Additional Feature CP would easily replace the whole Bind Operator Proposal. However there are some open questions, such as the implementation of removeEventListener('click', +> console.log) or even whether CP is worth introducing a new operator.

js-choi commented 6 years ago

Right now, [extension methods are not] covered anywhere in the pipeline proposal, yet [they are] quite similar to the pipelines.

Thanks for looking at the smart-pipelines proposal. For what it’s worth, the smart-pipelines proposal already discusses all three forms of the current proposal for the :: operator. I’d just like to make sure that you’ve read that section, since it does address extension methods, though maybe not by that particular term.

Although Feature PF does subsume, my current opinion is that function application and binding are orthogonal (as I say in #107). Being able to distinguish v |> join(#, ', ') from v |> #.join(', ') and v |> #::join(', ') is a desirable feature, since all three are reasonable interpretations of v |> join(', '). Because of that semantic ambiguity, v |> join(', ') is intentionally an early syntax error.

I’ll examine the rest of this more closely when I can—right now I’m hurrying to finish the explainer and specification as much as I can before the London TC39 meeting tomorrow. Thanks again; I’ll be thinking about this for the next days. Let me know if you read that existing section on ::.

cshaa commented 6 years ago

Oh, I've seen this part but I stopped at “But the other two use cases (infix ::) are not addressed by smart pipelines.” since I thought Ah, I already know +> console.log 😀

I'm glad that you've thought about the possible coexistence of :: and smart pipelines. My point here is mostly that if we moved the pipelining part of :: to the smart pipes spec, it would speed up its now-stagnant progress and ensure it will be consistent with the rest of smart pipes. And also that the pipelining part of :: is the only relevant one, the rest can be pretty much dropped once smart pipelines arrive.

Nevertheless I wish you good luck at the TC39 meeting! I'm pretty sure that smart pipelines are the future of JavaScript and you will kick a*! 💪

js-choi commented 6 years ago

Thanks for the kind words. As the existing :: section says, if smart pipelines receive interest at TC39, then that would open up the possibility of simplifying ::, letting it focus purely on binding. I have planned to reach out to ::’s authors, as well as the authors of other proposals, to discuss this sort of intersectionality, but I haven’t yet had time.

In any case, I won’t be presenting to TC39 myself; @littledan will be presenting, though @mAAdhaTTah and I together wrote the presentation slides. We’ll see what happens. Thanks again.

ljharb commented 6 years ago

There are benefits of the unary form (for “method extraction”) that are impossible to achieve with pipelines; namely, caching bound methods off of mutable objects (like builtin prototypes) for later use, while being robust against other code deleting or replacing those methods.

I will not be comfortable with the pipeline proposal advancing (and thus potentially killing the :: operator) unless this use case is met by something (in pipelines, or in a separate proposal) that the committee still considers palatable.

js-choi commented 6 years ago

@ljharb: Thanks for the heads up. I would be happy to investigate your use case. I know that TC39 is imminently meeting again, but, when you have time, could you write an example that concretely demonstrates the need to robustly cache bound methods from mutable objects? It’d make it easier for me to explore and/or specify, but I cannot yet find one in the current bind-operator proposal repository, in both its explainer and its issues. Thanks.

ljharb commented 6 years ago

Every place in my packages that i do Function.bind.call(Function.call, Array.prototype.map) or similar. It’s not a common technique because it’s so verbose, but making code robust against later prototype modification is very important. When pipeline was initially presented (and potentially dropping the bind operator came up) i (hope i) made it very clear that while i loved pipeline, i wouldn’t provide consensus for it to advance unless some form for method extraction remained tenable.

mAAdhaTTah commented 6 years ago

I will not be comfortable with the pipeline proposal advancing (and thus potentially killing the :: operator) unless this use case is met by something (in pipelines, or in a separate proposal) that the committee still considers palatable.

This concerns me a bit, because I don't think the F#-style has a good answer for this use case. The whole raison d'être of that proposal is to be minimal and lean on arrow functions to fill a lot of the gaps that are filled in Smart Pipeline by the lexical topic concept.

That said, F#-style isn't really intended to kill ::, as the binding use cases are not served by an F#-style pipeline, so maybe it's not an issue. I'm generally of the opinion that solving smaller problem spaces separately is a better approach, hence preferring F#-style.

Does a method extraction syntax, bind op or otherwise, have to advance in tandem or is leaving the possibility open enough to overcome your objections?

ljharb commented 6 years ago

@mAAdhaTTah note that i don’t think pipelines have to provide for method extraction, i just want to make sure that something - including another proposal - can solve for it. It would be sufficient if some proposal exists, is intended to be advanced, and the committee response is positive.

js-choi commented 6 years ago

@ljharb: That’s also good to know. Robust method extraction also might be orthogonal to Proposal 4 (Smart Pipelines) with Feature PF, similarly to how it is orthogonal with @mAAdhaTTah’s Proposal 1 (F-sharp Style Only with await). I’ll investigate your packages for cases of Function.bind.call, and I’ll see if pipeline function can accommodate them.

If not, then I would be happy to help out with a proposal for robust method extraction, whether it is with the original binding operator or with another operator. I’ll also search the previous TC39 meetings’ notes for your discussion of robust method extraction. It must have been before last September, or else I would have already seen it…

@mAAdhaTTah: It’s short notice, but we might want to update the presentation in anticipation of this discussion about our proposals’ relationships with method extraction and to address @ljharb’s concerns. I don’t yet know how though. We might have to quickly look at @ljharb’s packages and the prior TC39 meeting notes, later today, then figure out where to go from there…

mAAdhaTTah commented 6 years ago

@js-choi Let's not, imo; some of the slides are already pretty jam-packed, with a lot for the committee to digest. Both proposals leave open the possibility of method extraction; let's see what direction we get (if any) from the committee at this meeting and proceed from there.

ljharb commented 6 years ago

I agree that this presentation doesn’t need to address it; pipeline isn’t seeking advancement this go-round.

js-choi commented 6 years ago

@mAAdhaTTah, @ljharb : Understood.

I had forgotten that a discussion about robust method pre-extraction happened last September. By the time they returned to it in November, time was up. January was skipped while preparing for this, though @ljharb gave a comment about the importance of the prefix ::, as well as recently that it must be robust against delete Function.prototype.bind. ljharb/function.prototype.name gives examples of this pattern. There has been discussion of method-binding caching in tc39/proposal-bind-operator#46, in which a WeakMap is associated with each realm.

In any case, pipelines, including smart pipelines, and robust cached method pre-extraction can coexist. Using ljharb/function.prototype.name/implementation.js, line 19 as an example of useful coexistence:

return fn
|> &Function.prototype.toString
|> &String.prototype.match(#, classRegex)
|> !!#;

…which is just:

return !!&String.prototype.match(
  &Function.prototype.toString(fn),
  classRegex);

This security robustness is quite a different use case than simply wanting to express the callbacks in promise.then($ => console.log($)) more tersely, as well as the other goals of smart pipelines as well as the goals of other pipelines: most importantly, the untangled composition of expressions and calls.

I think secure cached method binding is out of scope for smart pipelines, even with Feature PF. It is also out of scope for @mAAdhaTTah’s Proposal-1 pipelines. These are orthogonal use cases. They cannot kill each other.

I will have to look into adding examples that use both pipelines and secure method extraction to the smart-pipelines explainer…

js-choi commented 6 years ago

@ljharb: I’ve pushed a commit to the smart-pipelines explainer that clarifies how it does not address or preclude an operator for robust method extraction. You can see some hypothetical examples in its section about the :: proposal. When you can, let me know if this ameliorates your concerns for now. Thanks. (You can also track js-choi/proposal-smart-pipelines#10.)

ljharb commented 6 years ago

It does somewhat :-) I’ll also be interested to hear the committee’s opinion on a proposal for method extraction.

babakness commented 6 years ago

I find that the order of operations useful. For example, lets say you have this sample library

https://gist.github.com/babakness/ba3c2ac03b030cecceca8a64877a79f4

export class Placeholder {}
export const _ = new Placeholder()
export const isPlaceholder = placeholder => placeholder instanceof Placeholder

export function bind( ...placeholders ) {
  return ( ...fillers ) => this.call( this, ...placeholders.map(
    item => isPlaceholder( item ) ? fillers.shift() : item
  ) )
}

Then you can do

import { _ , bind } from 'cool-stuff'

const getPointOnLine = (slope,offset,x) => slope * x + offset

const pointOnLine = document.querySelector('#whatever').value
  |> Number
  |> getPointOnLine::bind( .5, 10,  _ )

The fact that :: runs before |> is useful. One could have different version of bind that works like pipeline as well

import { _ , bind } from 'different-cool-stuff'

const getPointOnLine = (slope,offset,x) => slope * x + offset

const pointOnLine = document.querySelector('#whatever').value
  |> Number
  |> ( getPointOnLine |> bind( .5, 10,  _ ) )

But it is less intuitive.

szhu commented 5 years ago

One advantage I see with the bind operator is that there is no illusion of what is actually being done -- you're calling the RHS function with the LHS set as this (or the first arg, depending on proposal). The pipelines syntax seems to reflect how the operator is meant to be used rather than implemented, so someone who learns about pipelines could get confused about its semantics even if they're familiar with the rest of JavaScript.

It appears that progress with this proposal appears to be moving faster than bind operator proposal -- can someone explain why the community favors pipelines? (I know it's likely that this discussion has already happened, but I can't find it, so thanks in advance.)

TehShrike commented 5 years ago

The bind proposal and pipeline proposal are generally desired by two different sub-communities - the pipeline operator is desired primarily by functional-programming advocates, and the bind operator is desired primarily by class-based-programming advocates.

I'm not sure what role that played in this proposal attracting so much attention.

mAAdhaTTah commented 5 years ago

I'll also add that the bind operator had momentum before it stalled out. The initial idea of the bind operator covered a lot of different usages, and it stalled out on speccing some of those behaviors.

mmis1000 commented 5 years ago
function makeExtension(fn) {
  return function argumentReciever() {
    const arg = arguments
    return function thisReciever(self) {
      return fn.apply(self, arg)
    }
  }
}

var say =  makeExtension(function (word) { 
  console.log (this.name + ': ' + word)
  return this
})

;({name: "jack"}) |> say('Hello World')

I though, some of the usages may be simply described with a helper function to achieve the style of extension methods.

And this style would be nice to experiment new stage 1/2 methods as it can be used safely without pollute the prototype chain.

It could benefit future proposals by allow you to test brand new methods in production to see if it is really helpful or not without worry about it would breaks everything someday, or simulate virtual methods on native object.

However, the resolve order of pipeline operator and bind operator is reversed. Means you can't write

;({name: "jack"}) 
   |> say('Hello World')
   .toString()

because it world be parsed as

;({name: "jack"}) 
   |> (say('Hello World')
   .toString())
tabatkins commented 3 years ago

Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax.

This means it replaces some of the use-cases for :: (namely, creating method-like functions for fluent interfaces), but there are still remaining use-cases that it doesn't address and doesn't intend to.