bind operator (was: arrow function syntax simplified)
On Wed, Mar 28, 2012 at 1:37 PM, David Herman <dherman at mozilla.com> wrote:
On Mar 27, 2012, at 9:14 PM, Russell Leggett wrote:
I'm sure this is a bit of a tangent, but the other major related case is passing a "method" as an argument but needing to retain the correct "this". Obviously, that is what bind was meant for, but that is inconvenient when passing methods for the same reason it would be inconvenient for anonymous functions.
What if we had a shorthand for bind that worked in both of these cases...
I have a strawman on this topic:
I agree it's a real need. Just like ... will provide convenient, high-fidelity (e.g., insensitive to Function.prototype monkey-patching) syntax for .apply, we should also have an operator that provides convenient, high-fidelity syntax for .call and .bind. I believe a single operator can provide both.
Ah, there you go. I figured I wasn't the first to think of it. I think it might be worth talking about this in relation to the shorthand function syntax, because it could pull the lexical this issue out of that debate. Then we could reduce the arrows to something simpler: -> for blocks, => for
expressions with implicit return. It would also solve the related issue of this binding when passing methods as callbacks, which is also a major issue in a lot of apis, requiring extra parameters for the "this".
On Wed, Mar 28, 2012 at 2:02 PM, Russell Leggett <russell.leggett at gmail.com>wrote:
Ah, there you go. I figured I wasn't the first to think of it. I think it might be worth talking about this in relation to the shorthand function syntax, because it could pull the lexical this issue out of that debate. Then we could reduce the arrows to something simpler: -> for blocks, => for expressions with implicit return. It would also solve the related issue of this binding when passing methods as callbacks, which is also a major issue in a lot of apis, requiring extra parameters for the "this".
To me, the biggest problem when you need bound this is that you have to keep around the bound version so you can remove it. For example:
elem.addEventListener('click', onClick.bind(this)); ... elem.removeEventListener(type, ???);
I can't remove the bound listener unless I saved a reference to it, since another bind with the same callback and object reference gets a new bound function. This adds a lot of extra work, and makes it easy to leak memory.
If the function already had a bound this by the way it was defined, I don't need to keep an extra reference around (this assumes some class syntax that allows the same function shorthands:
class Foo { register(elem) { elem.addEventListener('click', onClick); }
unregister(elem) { elem.removeEventListener('click', onClick); }
onClick(e) => do { ... } }
I'm not sure how to quantify this, but I believe that if such a bind operator were available, it would be overwhelmingly be used to simulate lexical |this|.
If syntax is about optimizing the common case, then shouldn't we just provide a function form which lexically binds |this|?
On Wed, Mar 28, 2012 at 2:38 PM, John Tamplin <jat at google.com> wrote:
On Wed, Mar 28, 2012 at 2:02 PM, Russell Leggett < russell.leggett at gmail.com> wrote:
Ah, there you go. I figured I wasn't the first to think of it. I think it might be worth talking about this in relation to the shorthand function syntax, because it could pull the lexical this issue out of that debate. Then we could reduce the arrows to something simpler: -> for blocks, => for expressions with implicit return. It would also solve the related issue of this binding when passing methods as callbacks, which is also a major issue in a lot of apis, requiring extra parameters for the "this".
To me, the biggest problem when you need bound this is that you have to keep around the bound version so you can remove it. For example:
elem.addEventListener('click', onClick.bind(this)); ... elem.removeEventListener(type, ???);
I can't remove the bound listener unless I saved a reference to it, since another bind with the same callback and object reference gets a new bound function. This adds a lot of extra work, and makes it easy to leak memory.
If the function already had a bound this by the way it was defined, I don't need to keep an extra reference around (this assumes some class syntax that allows the same function shorthands:
class Foo { register(elem) { elem.addEventListener('click', onClick); }
unregister(elem) { elem.removeEventListener('click', onClick); }
onClick(e) => do { ... } }
I think that's a very specific use case. Not that its uncommon, its just that bound this is quite common in my experience without needing to hold the reference. One possibility for your case, though, would be if the bind operator always returned the same function. That would make it different than the way the bind function works, but it has a certain amount of sense. If this.onClick always refers to the same function, why not ::this.onClick? Of course I know that gets a little tricky. What is it returning after all? Do we create some concept of a method pointer? Something like C# delegates? C# delegates can wrap a static function or instance method and can use == to compare equality.
(Regarding the strawman syntax, I imagine the :: is going to have conflict with guards, no?)
On Wed, Mar 28, 2012 at 3:17 PM, Kevin Smith <khs4473 at gmail.com> wrote:
I'm not sure how to quantify this, but I believe that if such a bind operator were available, it would be overwhelmingly be used to simulate lexical |this|.
If syntax is about optimizing the common case, then shouldn't we just provide a function form which lexically binds |this|?
Thats not the only common case. It might be the most common, but I see a lot method pointers that need binding too. If you saw my proposal in the other thread, I was hoping to use this binding operator to separate the lexical binding of this from the shorter function syntax.
Thats not the only common case. It might be the most common, but I see a lot method pointers that need binding too.
Can you post an example?
Russell, I looked at the other thread more carefully and now understand what you're saying. Can't we use a bound |this| function for all these cases?
needsCallback(x => foo.bar(x));
needsCallback(x => this.bar(x));
// Suppose for a moment that -> also binds |this|
needsCallback((x, y) -> {
if (x) this.doX(x); else this.doY(y); });
It seems that with bound |this| functions, the need to explicitly bind |this| tends to fall away.
On Wed, Mar 28, 2012 at 9:57 PM, Kevin Smith <khs4473 at gmail.com> wrote:
It seems that with bound |this| functions, the need to explicitly bind |this| tends to fall away.
This is true for function literals that needs to be bound to the this
context in the scope where they're created, but not very helpful for
binding a function reference or for binding to objects other than this
.
In my own code, the first case is quite common - mostly functions that's
defined ones and gets bound to multiple objects. That being said - the most
common case I encounter is the anonymous-function-bound-to-this
scenario.
BTW, there's also some discussion on a bind operator at a ticket I opened on CoffeeScript's tracker, jashkenas/coffee-script#2136 (slightly based on the syntax proposed on "Sugar for *.prototype and for calling methods as functions" < esdiscuss/2012-February/020699>)
which might be related - tho the syntax itself is more CoffeeScript-y and probably less appropriate for ES6 (due to @ having a different meaning)
On Wed, Mar 28, 2012 at 3:57 PM, Kevin Smith <khs4473 at gmail.com> wrote:
Russell, I looked at the other thread more carefully and now understand what you're saying. Can't we use a bound |this| function for all these cases?
needsCallback(x => foo.bar(x)); needsCallback(x => this.bar(x)); // Suppose for a moment that -> also binds |this| needsCallback((x, y) -> {
if (x) this.doX(x); else this.doY(y); });
It seems that with bound |this| functions, the need to explicitly bind |this| tends to fall away.
Yeah, I mean look - it may be that having short function with bound this and implicit return can get the job done. Of course, it seems so easy with just one argument called x. Its a little different if it looks more like:
needsCallback((event,data) => foo.bar(event,data));
needsCallback((element,index,array) => this.bar(element,index,array));
Would you shorten or put in dummy arg names?
needsCallback((e,d) => foo.bar(e,d));
needsCallback((a,b,c) => this.bar(a,b,c));
What if you didn't want to worry about the arguments? I mean, there's no reason you should have to make this know the contract between the function call and the callback params. I guess we could use rest params.
needsCallback((args...) => this.bar(args...));
But obviously that starts to seem like a code smell, especially if you have multiple:
doSomething({
success: (args...) => this.success(args...),
error: (args...) => this.error(args...)
});
In this case it doesn't really beat bind, and maybe it doesn't have to, but in relation to the short function syntax thread, it's possible that this makes sense as a way of composing different parts.
I find this proposal backwards. It requires an expeession as the right hand side. Alex proposed something similar a long time ago. He suggested using '!' instead of '.'. This is important because the common use case we want to solve is to make 'obj.foo.bind(foo)' be written as 'obj!foo'. With your proposal I have to do let f = obj.foo; obj::f which is even longer than the original.
Another important, but orthogonal part of his proposal was to have obj!meth === obj!meth
expression!propertyName
// desugars to
// WeakMap to cache the lookup. let objectMap = new WeakMap;
do { let object = expression; let map; if (!(map = objectMap.get(object)) { map = new Map; objecMap.set(object, map); } let result; if ((result = map.get("propertyName"))) return result; result = object.propertyName.bind(object); map.set("propertyName", result); result; }
One option would be to use the function instead of the property name as the key in the inner map.
On Wed, Mar 28, 2012 at 5:19 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:
I find this proposal backwards. It requires an expeession as the right hand side. Alex proposed something similar a long time ago. He suggested using '!' instead of '.'. This is important because the common use case we want to solve is to make 'obj.foo.bind(foo)' be written as 'obj!foo'. With your proposal I have to do let f = obj.foo; obj::f which is even longer than the original.
Yes, this was the way that my proposal worked as I mentioned it in the other thread (sorry, this is getting confusing now) === from my orginal post in the short function thread === //here the # acts as both a . and bind in one needsCallback(foo#bar) needsCallback(this#bar)
//in this case, it obviously doesn't access a property, it just does a bind needsCallback(this#function(x,y){ if(x){ this.doX(x); else{ this.doY(y); } }); //or with the new syntax needsCallback(this#(x,y)->{ ... });
I used # instead of !, but effectively the same idea.
Another important, but orthogonal part of his proposal was to have obj!meth === obj!meth
expression!propertyName
// desugars to
// WeakMap to cache the lookup. let objectMap = new WeakMap;
do { let object = expression; let map; if (!(map = objectMap.get(object)) { map = new Map; objecMap.set(object, map); } let result; if ((result = map.get("propertyName"))) return result; result = object.propertyName.bind(object); map.set("propertyName", result); result; }
One option would be to use the function instead of the property name as the key in the inner map.
Yes, this was something I mentioned to John Tamplin regarding his concern above about unregistering a handler.
I'm writing up a more detailed proposal that I will post soon.
On Mar 28, 2012, at 2:19 PM, Erik Arvidsson wrote:
With your proposal I have to do let f = obj.foo; obj::f which is even longer than the original.
That's not a fair argument. There's no need for the let-binding; you can write
obj::obj.foo
or
::obj.foo
Now, you might feel that it's more intuitive for the latter form to be expressed with an infix operator. I'm sympathetic to that. But my operator is more expressive, because it can also express .call, which Alex's ! operator can't:
o1::o2.foo(x, y, z) ~= o2.foo.call(o1, x, y, z)
I would love to have built-in syntax for doing all three ABC methods (.apply/.bind/.call), so that's what I find appealing about the :: syntax. It's nice conservation of special forms that :: in conjunction with ... can accommodate all three. But perhaps we should explore two separate new forms.
Another important, but orthogonal part of his proposal was to have obj!meth === obj!meth
I'm not sure I have an opinion about this part yet. Can you explain the rationale?
On Thu, Mar 29, 2012 at 15:34, David Herman <dherman at mozilla.com> wrote:
On Mar 28, 2012, at 2:19 PM, Erik Arvidsson wrote:
With your proposal I have to do let f = obj.foo; obj::f which is even longer than the original.
That's not a fair argument. There's no need for the let-binding; you can write
obj::obj.foo
I'm not sure I follow? Replace obj with an arbitrary expression and it becomes evident that a let binding is needed
::obj.foo
I guess this one would work but I find it a bit strange to have this as prefix operator.
::object.foo.bar().baz
which is the same as
do { let tmp = object.foo.bar(); tmp::tmp.baz }
Now, you might feel that it's more intuitive for the latter form to be expressed with an infix operator. I'm sympathetic to that. But my operator is more expressive, because it can also express .call, which Alex's ! operator can't:
o1::o2.foo(x, y, z) ~= o2.foo.call(o1, x, y, z)
That is pretty nice but I think we covered the two most common uses by introducing super and spread already
I would love to have built-in syntax for doing all three ABC methods (.apply/.bind/.call), so that's what I find appealing about the :: syntax. It's nice conservation of special forms that :: in conjunction with ... can accommodate all three. But perhaps we should explore two separate new forms.
Another important, but orthogonal part of his proposal was to have obj!meth === obj!meth
I'm not sure I have an opinion about this part yet. Can you explain the rationale?
The classic use case that everyone runs into is addEventListener/removeEventListener.
eventTarget.addEventListener(type, this.handleFoo.bind(this)); ... eventTarget.removeEventListener(type, this.handleFoo.bind(this)); // OOPS
On Mar 27, 2012, at 9:14 PM, Russell Leggett wrote:
I have a strawman on this topic:
I agree it's a real need. Just like ... will provide convenient, high-fidelity (e.g., insensitive to Function.prototype monkey-patching) syntax for .apply, we should also have an operator that provides convenient, high-fidelity syntax for .call and .bind. I believe a single operator can provide both.