00:00
<Justin Ridgewell>

Call-this + pipe have the advantage of no overlap,

I think I just refuted that.

00:00
<Justin Ridgewell>
It exactly overlaps with pipeline+uncurry
00:00
<TabAtkins>
No? You said call-this overlaps with uncurry.
00:01
<TabAtkins>
Right, because call-this and uncurry are the same thing in different syntaxes.
00:03
<Justin Ridgewell>
My goal here is to allow fluent APIs with acceptable synatx
00:03
TabAtkins
is heading home for the day, will have to continue later
00:03
<Justin Ridgewell>
I don't think pipeline and normal methods looks appealing
00:03
<Justin Ridgewell>
Pipeline as a chain of free functions is fine
00:04
<Justin Ridgewell>
But mixing in a chained method on an object in the pipe and it starts to have warts
00:04
<Justin Ridgewell>
Mixing methods and bind-ops works perfectly
04:53
<ljharb>
pipeline can’t handle call-this/uncurry without a special operator
04:54
<ljharb>
the operator could be call-this, or bind-this, or something else, but it can’t be done with pipeline alone
04:55
<ljharb>
i agree that bind-this is the one that fits into an OOP method chain without pipeline, which is useful.
20:07
<TabAtkins>
So the issue that's gonna continue to bite is that, from the feedback we've gotten, having bind-this able to do essentially the same thing as pipe is a Problem. It might turn out to be sufficiently okay to look past, but as it is this is the precise sort of overlap that has been called out as making committee members uncomfortable.
20:10
<TabAtkins>
And the arguments against "fluent API via bind-this" are precisely the same arguments against "fluent API via F# pipe", which is that it will encourage authors to write libraries intended specifically to be called this way, which means they're harder or awkward to call in the traditional way, and won't work with any other feature that expects functions, like something that takes callbacks - they'll have to be wrapped in an arrow func instead. (For example, it won't work with PFA, if you want the receiver to be the thing placehold'd.)
20:11
<TabAtkins>
Part of the reason we fought so hard for Hack pipes is precisely because it works reasonably well for all of these cases. Yes, it's not the local minimum for fluent APIs, but it's hovering just outside of it, and without the additional downsides that come from seeking that minimum.
20:14
<TabAtkins>

And I'll note that in the previous code sample, the pipe code was written badly. When done correctly, it's roughly identical to the bind-this code:

array
  .filter(...)
  |> uniq(##)
  .find(...)
  .something();
20:15
<TabAtkins>
The only difference (an unavoidable one) is, as always, having to put the topic value in the arglist, rather than it being passed implicitly in some way. But that's it, vs the extra code and additional indentation that the previous code sample unnecessarily burdened it with.
20:56
<ljharb>

that pipe example is fine with me, to be sure. however, if uniq expects a this value then you'd have to (with your proposal) do this:

array
  .filter(...)
  |> uniq::(##)
  .find(...)
  .something();

instead of, as with the bind-this proposal:

array
  .filter(...)
  |> ##::uniq()
  .find(...)
  .something();
21:54
<TabAtkins>
Yes, so you, of course, would not write a library with export function uniq() { return [...new Set(this)]; }, you'd write it as export function uniq(arr) {...}
21:55
<TabAtkins>
Rather than there now being three potential ways to write a function (as a method, as a this-using free fucntion, or as a this-less free function), we stick with the two that we currently have.
22:08
<TabAtkins>
But if you'd extracted uniq() from a class because of robustness, then yes, you'd write it in the first style with the call-this operator.
22:09
<TabAtkins>
The alternative is an uncurry operator that just produces a fresh function a la fn.call.bind(fn)
22:09
<TabAtkins>
Then it's just |> uniq(##)
22:16
<jschoi>
(I am neutral between a bind-this operator receiver |> #::fn() and a call-this operator receiver |> fn::(#), but I would rather have either than a demethodize operator receiver |> (::fn)(#).)
22:39
<ljharb>
Those three already exist
22:40
<TabAtkins>
You can't just say things like that when you mean "you can do these by calling Function methods" ^_^
23:37
<Justin Ridgewell>

And I'll note that in the previous code sample, the pipe code was written badly. When done correctly, it's roughly identical to the bind-this code:

array
  .filter(...)
  |> uniq(##)
  .find(...)
  .something();
Ok, that looks a little better.
23:40
<Justin Ridgewell>
And the arguments against "fluent API via bind-this" are precisely the same arguments against "fluent API via F# pipe", which is that it will encourage authors to write libraries intended specifically to be called this way, which means they're harder or awkward to call in the traditional way, and won't work with any other feature that expects functions, like something that takes callbacks - they'll have to be wrapped in an arrow func instead. (For example, it won't work with PFA, if you want the receiver to be the thing placehold'd.)
I don't think this is true. F# coded a very specific (very restrictive) only-one-arg style that Hack style avoids. Bind-op doesn't suffer from this, since args can be passed. It just happens that Bind-op also doesn't require a topic token to run.
23:41
<TabAtkins>
No, F#-style just requires your function to accept one of its arguments as a second, unary call. There's no fundamental difference between fn(a, b)(c) and c::fn(a, b), they're both just accepting one of their arguments in a special way that's not part of the arglist.
23:42
<Justin Ridgewell>
There's a huge difference with creating a closure and passing arguments to a single function.
23:43
<TabAtkins>
function fn(a, b) { this=>{...}} can even have the exact same body as a this-using function. ^_^
23:44
<TabAtkins>
There's not really? Like, sure, yeah, they're different, and there's internal stuff. But semantically they're the same. JS distinguishes them; Haskell doesn't; it's just a syntax choice.
23:46
<Justin Ridgewell>
We shot down F# because of the temporary closures it requires to do anything non-trivial. Even if the runtime output would be the same, I don't think it's fair to compare bind-op to F#.
23:52
<TabAtkins>
That was one of the reasons, yes. (Luckily one that excited the impls so we could lean hard on it.) It was definitely not the only.
23:53
<TabAtkins>
The ecosystem-forking effect (libraries being written explicitly for pipeline, authoring all their functions as unary-returning) was another reason; we did not want that to happen. And that precise argument applies equally to bind-this.
23:57
<jschoi>

For what it’s worth, a few months ago, I raised the ecosystem-forking concern regarding bind-this before in this channel, since it had already been raised in plenary by Waldemar Horwat against Hax’s extensions proposal.

At the time I raised that concern about bind-this here, Jordan (and I think maybe even Tab?) said that the ecosystem-forking risks between bind-this and extensions were “different”, but I still can understand why it may be a concern. I wonder what Waldemar would think, being the one who had raised it for extensions.

23:58
<Justin Ridgewell>

The ecosystem-forking effect… And that precise argument applies equally to bind-this.

I don't agree with this. My understanding of the consensus was encouraging a fully functional, make closures-everywhere was a bad idea. Not that ecosystem fork is a huge issue, and certainly not enough that we'd block entirely.