Closed cshaa closed 3 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 ::
.
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*! 💪
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.
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.
@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.
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.
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?
@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.
@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…
@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.
I agree that this presentation doesn’t need to address it; pipeline isn’t seeking advancement this go-round.
@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…
@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.)
It does somewhat :-) I’ll also be interested to hear the committee’s opinion on a proposal for method extraction.
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.
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.)
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.
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.
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())
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.
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:Unary Bind operator
::obj.fn
Meaning:
obj.fn.bind(obj)
Use case:
addEventListener('click', ::console.log)
Problem: People don't like the syntax – it's completely different from
::
operators in other languages and if feels a bit weird that::a.b.c
means the same asa.b::a.b.c
.Problem: It produces a new function every time. This wouldn't work in the original proposal:
removeEventListener('click', ::console.log)
However the problem can be fixed quite simply using WeakMap internally to return the same bound function every time.Status: The more universal Pipeline Functions proposal makes it quite redundant. Furthermore it solves the syntax problem –
+>
isn't clear at first sight, but after looking it up, the developers should recognize it as a “crossbreed of the pipeline operator|>
and an arrow function=>
”. The problem with returning a new function every time is however more complicated to solve with PF than with::
. We could therefore leave(+> foo.bar) === (+> foo.bar)
as undefined behavior until we find a systematic way to enforce equality..
Context Pipeline operator
context::fn(...args)
Meaning:
fn.apply(context, args)
Use case:
arr::unique()::sort()::join(', ')
Extension methods. Similar to the OP in #101 but IMHO cleaner, since we're explicit about what is an extension method and what is a property on the object itself.
Problem: The only major problem with this feature is that, despite it's quite uncontroversial and requested often by various people, it's being delayed by the rest of the proposal. See tc39/proposal-bind-operator#44 for more discussion.
Status: Right now, this feature isn't covered anywhere in the pipeline proposal, yet it's quite similar to the pipelines. There have been opinions that, because of the similarity with pipelines and the controversy of the other two variants of
::
, we should change it to something more consistent with the pipeline operator. User @fatcerberus proposed->
instead of it, I would peronally go with.>
or:>
.Differences from
|>
: There is some overlap between the Context Pipeline and the ordinary Pipeline Operator in terms of the extension method functionality. If you wanted to use|>
, you could pretty much use the first parameter instead ofthis
and rewrite the use case of this topic toarr |> unique |> sort |> join(#, ', ')
. You can see that the syntax looks lighter than with::
. However it doesn't feel exactly JavaScript-y, we're all used tothis
being in the spotlight, especially with native types likestring
orarray
. And the#
injoin(#, ', ')
is just weird.My proposal: I don't know if these arguments are enough to add the Context Pipeline to JavaScript despite the ordinary Pipeline can do much of its tricks. However I'd add the operator to the Smart Pipeline Proposal and let the community (and TC39) express their opinion. This would specifically mean removing Context Pipeline from the Bind Operator Proposal and replacing it with Additional Feature CP (i. e. Context Pipelines) for Smart Pipelines. This additional feature would introduce a new infix operator, for example
:>
, where these rules would apply:expression_a :> expression_b
translates to(expression_b).call(expression_a)
expression_a :> expression_b( ...argument_list )
translates to(expression_b).apply(expression_a, argument_list)
If I rewrote the use case again using this new syntax, it would be:
arr :> unique :> sort :> join(', ')
, which is even cleaner than with the ordinary pipeline..
Binary Bind operator
context::fn
:Meaning:
fn.bind(context)
Use case: Eh, binding functions to context I guess?
Status: If Additional Feature PF and NP were accepted together with my proposed CP, Binary Bind operator could be replaced by
+>( context :> fn(...) )
. And with BP it could be written as+>{ context :> fn(...) }
which is maybe even cleaner..
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.