tc39 / proposal-bind-operator

This-Binding Syntax for ECMAScript
1.74k stars 30 forks source link

What's keeping this from Stage 1? #24

Closed dead-claudia closed 7 years ago

dead-claudia commented 8 years ago

Edit: Keeping this list up to date.

TL;DR: See title.

This already has transpiler support, and has definitely fulfilled at least most of the requirements for Stage 1. These are the ones I can tell are very clearly, unambiguously met (pulled from the process document):

I'm just wondering what's holding this back from Stage 1.

zenparsing commented 8 years ago

I'm the champion. : )

There are a couple of reasons that I haven't attempted to advance this proposal with TC39:

First, when I presented this proposal back in March, I received a fair amount of pushback on the following grounds:

On balance, TC39 wasn't particularly supportive. We went ahead with getting it into Babel, though, in order to gather some feedback. That feedback has been really useful.

I believe that the next step is to decide whether we would be better off splitting the proposal into separate "method extraction" and "functional pipelining" proposals. I'm cautiously in favor of that.

The second big reason that I haven't advanced this proposal is that I don't think this is the right time for adding syntax to JS, with the big exception of async functions. I think we need to focus on just async functions for ES7/2016/whatever.

Too much syntax too quickly isn't a good thing, especially given the huge amount of syntax added with ES6.

dead-claudia commented 8 years ago

Okay. Understandable.

shovon commented 8 years ago

I'm curious about functional pipelining. How would that work out?

dead-claudia commented 8 years ago

Edit (Aug. 12, 2016): This is merely a strawman proposing a change to the proposal, not the actual proposal itself. Just to clarify.


@zenparsing I do like the idea of separating these two ideas.

First of all, method binding a la Java would look really nice. It could even open up the possibility of a property shorthand.

getBooks().then(JSON.parse).then(books => books.map(book => new Book(book)))

// Simplify constructor call
getBooks().then(JSON.parse).then(books => books.map(new::Book))

// Shorthand binding of book => book.length
books
  .then(books => books.filter(compose(::length, atLeast(5))))
  .then(books => books.map(new::Book))

Shorthand for book => book[prop]
books.then(books => books.map(::[prop]))

It would be incredibly convenient, and it is also very commonly used. It's also extremely optimizable.

As for a pipelining syntax, I was kinda thinking of something like this:

function *map(f) {
  for (const i of this) yield f(i)
}

function* concat() {
  for (const i of this) {
    if (i[Symbol.iterator]) yield* i
    else yield i
  }
}

function *flatten() {
  yield* this
    ->map(x => x->flatten())
    ->concat()
}
dead-claudia commented 8 years ago

And for the dynamic this problem, it's already come up in TypeScript land because JS already calls methods by delegation (prototypal vs classical), and there's no way to properly type Function.prototype.bind and friends without it.

class A {
  foo() { return "A!" }
  bar() { console.log(this.foo()) }
}

class B {
  foo() { return "B!" }
}

console.log(new B().bar()) // "B!"
ssube commented 8 years ago

@impinball I think the method being bound should be on the right of the operator, for consistency both internally and with other languages. Type::new or Type::static and instance::method provide a predictable way to reference a call of some sort, even though new is an operator (putting it before the type seems like you're inverting the application). All forms can desugar into simple arrow functions or even just function calls.

dead-claudia commented 8 years ago

@ssube Then how would you do something like Base.new.bind(Base)? It's not an unheard of name, and it could be the right name for some data types.

ssube commented 8 years ago

@impinball I think the property access should take care of that: instance::[new]. I'm not sure I've seen that in use, but explicitly marking it as the new property should work.

dead-claudia commented 8 years ago

@ssube I like that idea.

On Thu, Sep 17, 2015, 23:57 Sean Sube notifications@github.com wrote:

@impinball https://github.com/impinball I think the property access should take care of that: instance::[new]. I'm not sure I've seen that in use, but explicitly marking it as the new property should work.

— Reply to this email directly or view it on GitHub https://github.com/zenparsing/es-function-bind/issues/24#issuecomment-141336546 .

zenparsing commented 8 years ago

@shovon A pipelining operator (found in some languages like F# as |>) would address the "iterlib" use case described in this proposal. It would look like:

function fn(a, b) {
    console.log([a, b].join(", "));
}

"hello"->fn("world");
// > "hello, world"

Where the left hand side is used as the first arg in call to the function on the right hand side.

dead-claudia commented 8 years ago

Closing in favor of #26, as the initial questions have been answered.

deontologician commented 8 years ago

It seems like this issue isn't really resolved since a bunch of the community want to see it move forward, and it still isn't. Is it possible to reopen this so people have a place to discuss the fact that it's being blocked? (re: https://github.com/zenparsing/es-function-bind/issues/30#issuecomment-173977550) Right now #30 has the most recent discussion but isn't actually about the progress of the proposal

zenparsing commented 8 years ago

@deontologician Happy to reopen.

I would be willing to present this proposal to the committee again, but would like a co-champion.

deontologician commented 8 years ago

Does the co-champion have to be on the committee already?

zenparsing commented 8 years ago

@deontologician Yes.

deontologician commented 8 years ago

Is there a list of people on TC39? I found http://tc39wiki.calculist.org/about/people/ but I'm not sure if it's up to date

donaldpipowitch commented 8 years ago

I really hope this proposal will be presented again to TC39. It just too useful for things like the 'iterlib' example, adding custom operators to observables without modifying the prototype (e.g. this ideal world) and so.

I just wrote a couple of helper functions to make selenium tests really easy to write:

// gets the third user, opens menu, clicks delete
await '.users'::eq(2)::get('.menu')::click()::get('.delete')::click();

Every helper function is async/await and awaits the previous this, if it is a Promise. This is super hard to write and use without ::.

RangerMauve commented 8 years ago

@donaldpipowitch You could just call .then() and have those functions return callbacks. :P

robotlolita commented 8 years ago

@ljharb

I would be willing to present this proposal to the committee again, but would like a co-champion.

Mouvedia commented 8 years ago

The first check needs to be unticked in light of the discussion in #35.

The last one needs to be unchecked mainly because of #27 and #36.

bergus commented 8 years ago

@Mouvedia I don't see why the last box had to be unchecked. These challenges have been identified now, haven't they? They don't need to be solved to for advancing to stage 1.

Mouvedia commented 8 years ago

@bergus true, that's one way of going about it.

dead-claudia commented 8 years ago

@Mouvedia To elaborate on that further, it's that those challenges might involve significant changes to the proposal (which are expected of stage 0 and stage 1).

caub commented 8 years ago

I don't get the point for this proposal

how ::doThing would be clearer than x=>x.doThing()? it's slightly shorter, but less readable.

Not completely related but what could be more interesting is a compose operator rather than this

Alxandr commented 8 years ago

@caub You're wrong in your transform though, at least to my understanding. ::x.doThing is the same as (...args) => x.doThing(args). It allows you to do elm.addEventListener('click', ::this.onClick) in a class for instance. Another great example is asyncFunc().then(::console.log) as opposed to what you do today which is asyncFunc().then(result => console.log(result)).

caub commented 8 years ago

::console.log is not necessary on node and recent browsers, (works without .bind(console), try Promise.resolve(1).then(console.log))

also it's (...args) => x.doThing(...args) @Alxandr

Ltrlg commented 8 years ago

While doThing seems to be an inappropriate identifier example for this syntax, it has been mentionned by isiahmeadows previously.

dead-claudia commented 8 years ago

@Alxandr

@caub was referring to my strawman idea, not the proposal's syntax. Sorry for not making that clear enough.

bsideup commented 7 years ago

The power of bind operator is unclear until you realize the real use case - method chaining.

How ::doThing would be clearer than x=>x.doThing() It wouldn't, more or less.

But consider the following:

import { pick, take } from "array-utils";
import { filterBy } from "../helpers";

myObj::pick("pets")::filterBy("owner")::take(5)

There are no other ways to do it nicely. The closest option is:

compose(pick("pets"), filterBy("owher"), take(5))(myObj)

And it's ugly, verbose and harder to explain to others. Plus it breaks "object first" semantic.

Syntax matters. The current proposal is so natural to the standard method call, where we have an object and we call a function on it. And, compared to prototype-based approach, it has two major advantages: 1) doesn't pollute the global scope 2) makes it clear when it's an instance function or not. There are different implementations of Extension Methods in different languages, and if we take Kotlin for example, it's totally unclear which function was called, and it's a PITA during the code review process. For instance: https://github.com/JetBrains/kotlin/blob/09e7e0da4dc03afe16acac27d1789f72a911f2a9/libraries/examples/browser-example-with-library/src/main/kotlin/sample/Hello.kt#L15

This code is unreadable until you open IDE because this function is defined as extension method: https://github.com/JetBrains/kotlin/blob/09e7e0da4dc03afe16acac27d1789f72a911f2a9/libraries/examples/kotlin-js-library-example/src/main/kotlin/sample/Main.kt#L11

So... This is all about the syntax, and it must differ.

caub commented 7 years ago

is myObj::pick("pets")::filterBy("owner")::take(5) like:

myObj.map(o=>o.pets).filter(o=>o.owner=='owner').slice(0,5)

(or maybe the slice inside the map, I don't understand well filterBy too)

Alxandr commented 7 years ago

@caub No it's not. Cause you're assuming that myObj is an Array (or something else that has .filter and .map on it's prototype). The really awesome thing about the bind operator is that it would enable you to do things like this:

document.querySelectorAll('div')
  ::filter(div => div.offsetHight > 10)
  ::map(div => div.childNodes[0])
  ::take(5);

More importantly, you could add "virtual" methods to anything. We're not just talking about arrays here.

fetch('some/url')
  ::asJson()
  ::renderAs(MyReactComponent, document.getElementById('root'));
caub commented 7 years ago
// NodeList.prototype.filter = Array.prototype.filter; // would be cool, but bad to do
const {call: filter} = Array.prototype.filter;

filter(document.querySelectorAll('div'), div => div.offsetHeight > 10)
  .map(div => div.firstChild)
  .slice(0,5);
const fetchJSON = (url, opts) => fetch(url, opts).then(r=>r.json());
fetchJSON('some/url')
  .then(data => ReactDOM.render(React.createElement(MyReactComponent, data), root))

in short, you never need that new operator, the only ossible advantage would be lazyness, static analysis.. but for those simple tasks, not really

bsideup commented 7 years ago

@caub you're always making an assumption that target object has this method.

i.e. the result of fetch has .json() method. Okay. Now I want it as protobuf object. Why should I rewrite my chain if I just want to change asJson() to asProtobuf() from 3rd party library?

Regarding prototypes - they are global. If one developer in one place with set .prototype.filter to one method, and another developer to something else - have fun debugging it! JS world is moving away from the global prototype modifications, and you propose the opposite.

Also, what if querySelectorAll will return different objects depending on some conditions? So you must know all results returned from it, plus you must assign filter to every prototype.

Bind operator will take any result, and can do some conditions inside as well, i.e. if list was returned - return filtered list, if it's a single object - check if it matches and return a list with a single item.

dead-claudia commented 7 years ago

By the way, Sergi, that general benefit is known and well discussed. (I also invite you to check out Trine, a utility library built to demonstrate what this can do.)

On Fri, Sep 23, 2016, 03:39 Sergei Egorov notifications@github.com wrote:

@caub https://github.com/caub you're always making an assumption that target object has this method.

i.e. the result of fetch has .json() method. Okay. Now I want it as protobuf object. Why should I rewrite my chain if I just want to change asJson() to asProtobuf() from 3rd party library?

Regarding prototypes - they are global. If one developer in one place with set .prototype.filter to one method, and another developer to something else - have fun debugging it! JS world is moving away from the global prototype modifications, and you propose the opposite.

Also, what if querySelectorAll will return different objects depending on some conditions? So you must know all results returned from it, plus you must assign filter to every prototype.

Bind operator will take any result, and can do some conditions inside as well, i.e. if list was returned - return filtered list, if it's a single object - check if it matches and return a list with a single item.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/tc39/proposal-bind-operator/issues/24#issuecomment-249123826, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBEngDZc73VWwnuc4OU0Ac7aWFX1vks5qs4I7gaJpZM4F-1e1 .

mikesherov commented 7 years ago

Would it be possible to at least advance the binary form of this operator to stage 1?

jQuery + LoDash as es6 import methods with chaining are a real use case that we'd all really be able to benefit from.

ljharb commented 7 years ago

Splitting up the functionality isn't going to work - since use of any form of the operator would need to be consistent with every form of the operator, I think they need to advance together or not at all.

billinghamj commented 7 years ago

I'm still really super keen to use this syntax... hopefully it can continue to be developed?

MeirionHughes commented 7 years ago

I really like the function binding case foo::method(param) and I think it would be an amazing feature.

If the lambda use-case (i.e. .bar(::method) ) is a source of contention though (to the point of nether getting through), could that not be separated out from the initial proposal?

ljharb commented 7 years ago

@MeirionHughes no - see two comments up.

niieani commented 7 years ago

@ljharb I really do not see why this absolutely cannot be split up.

Here's how I see it could be done:

Since both features would need separate implementations, the ::method syntax can simply be defined as illegal (reserved for future use) in the first proposal, so that a follow-up proposal can be safely made and discussed over the next year or two.

ljharb commented 7 years ago

@niieani the semantics of the unary form could absolutely alter the semantics of the binary form - it's entirely possible that advancing just the binary form could forever block shipment of the unary form, whereas coupling both together and deferring only temporarily blocks the binary form.

calebmer commented 7 years ago

On July 14 in https://github.com/tc39/proposal-bind-operator/issues/35#issuecomment-232807558, @bterlson said he and @mattpodwysocki would champion this proposal. Has this changed?

AlexGalays commented 7 years ago

@caub You're wrong in your transform though, at least to my understanding. ::x.doThing is the same as (...args) => x.doThing(args). It allows you to do elm.addEventListener('click', ::this.onClick) in a class for instance

You wouldn't want to write that though, as it's then impossible to remove the listener. Another great example of why we don't need more features leveraging the tricky this. I would love |> though.

dead-claudia commented 7 years ago

People already use Function.prototype.bind. IMHO, the unary operator doesn't really add any value because of arrow functions, though. I seriously hope the binary version makes it, though.

On Mon, Jan 23, 2017, 09:53 AlexGalays notifications@github.com wrote:

"@caub https://github.com/caub You're wrong in your transform though, at least to my understanding. ::x.doThing is the same as (...args) => x.doThing(args). It allows you to do elm.addEventListener('click', ::this.onClick) in a class for instance"

You wouldn't want to write that though, as it's then impossible to remove the listener. Another great example of why we don't need more features leveraging the tricky this. I would love |> though.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/tc39/proposal-bind-operator/issues/24#issuecomment-274508273, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBJ_u1-Yv4-HmgMGBCAFYGLIl-Pocks5rVL8CgaJpZM4F-1e1 .

Artazor commented 7 years ago

@isiahmeadows, completely agree!

jackmahoney commented 7 years ago

What can watchers do to move this proposal ahead?

jackmahoney commented 7 years ago

If others are wondering why this proposal is useful, it would add a lot of nice functional syntax making it easier to use functional libraries.

//es6
import { flatten } from 'library';
flatten(array.filter().map())
//es6+addition
import { flatten } from 'library';
array.filter().map()::flatten();
AlexGalays commented 7 years ago

@jackmahoney The pipeline operator (|>) proposal is made for that use case too. Except it's more functional and explicit as it doesn't bother with the annoying this.

https://github.com/mindeavor/es-pipeline-operator

mikesherov commented 7 years ago

Binary form also really useful for prollyfills and other experimental features. document::proposedMethodName() is way better than a broken document.proposedMethodName

slikts commented 7 years ago

The bind syntax would be a stepping stone for JS to eventually have native generic iteration methods. Having to use the Array iteration methods for other types is currently a significant pain point in the language.