Arrow binding

Alex Russell alex at dojotoolkit.org
Mon Apr 23 07:17:45 PDT 2012


On Mon, Apr 23, 2012 at 2:47 PM, Russell Leggett
<russell.leggett at gmail.com> wrote:
>
>
> On Mon, Apr 23, 2012 at 6:53 AM, Alex Russell <alex at dojotoolkit.org> wrote:
>>
>> Despite making repeated arguments for "soft binding", I'm pretty sure I
>> haven't outlined here what it actually would *be*. Now that we're looking to
>> add syntactic forms that create bound function objects (arrows and class
>> methods), perhaps it's time to get consensus for or against. Soft binding
>> has 2 properties that make it desirable:
>>
>>  * The global contract that methods can have their "this" re-set with
>> .call() and .apply() is maintained
>>  * Common-case usage avoids the hazards of unbound and mis-appropriated
>> "this" contexts. Most commonly, passing a method to a function which takes a
>> callback:
>>
>>     node.addEventListener("click", foo.bar);
>>
>> The language today has 2 types of functions:
>>
>>   * unbound: methods for which "this" is not fixed
>>   * hard-bound: methods bound by Function.prototype.bind()
>>
>> Crucially, we have no syntax which creates hard-bound methods which means
>> that they're not common (yet). To the extent that they are used, it is
>> explicitly through forms like:
>>
>>     node.addEventListener("click", foo.bar.bind(foo));
>>
>> Or through libraries:
>>
>>     dojo.connect(node, "onclick", foo, "bar");
>>
>> This means that most users of most functions can still use .call() and
>> .apply() without apprehension. Functions are still "just functions".
>
>
> That is only true for functions that actually use |this|. Even though bind
> is probably not used in force yet because of cross-browser worries, "var
> self = this" is used everywhere. Functions using that pattern are no more
> usable with call/apply than arrow functions.

"everywhere" is incredibly strong wording, and I think that in the
large, you're probably not correct. Some large % of code might
manually "call their scope" through closure binding, but even that
isn't an argument against soft binding.

>> The new forms we're adding (methods and arrows) have the potential to
>> change this radically, causing a large percentage of functions encountered
>> by programmers to have binding. If that binding is hard-binding, .call() and
>> .apply() break in the minds of users. Perhaps that's fine by you, but in
>> addition to being a contractual failure, it removes a form of genericness
>> which is unique in the language.
>
>
> Last I checked, the new method form is still a dynamic binding - otherwise
> it wouldn't work. So really, you're just talking about arrow functions. In
> the cases where arrow functions make the most sense (callbacks) you rarely
> want a dynamic |this| - the oddball case being event handlers primarily.

Having done this dance a couple of times, let me suggest to you that
the method form will *eventually* end up at a per-class getter on the
prototype which vends an instance function which is bound. People will
(reasonably) want binding of some sort.

>> What to do?
>>
>> One option is to barrel onward with either unbound functions, hard bound
>> functions, or some mix thereof. These are all painful in ways I don't need
>> to spend time here explaining. I propose a third alternative: soft binding
>> (aka "preferred binding"). It enables the following:
>>
>>     node.addEventListener("click", foo.bar.prefer(foo));
>>
>> While still allowing the following:
>>
>>     foo.bar.call(otherThis, …args);
>>
>> Functions with preferred bindings can still be re-bound either with new
>> preferred binding or with new hard binding (both forms vend new functions
>> objects and they do today).
>>
>> Here's a JSFiddle with an a quick ES5 desugaring + example:
>>
>>      http://jsfiddle.net/slightlyoff/739CS/20/
>>
>> Note that we need to re-define .call() and .apply() to be savvy to
>> preferences, but this doesn't seem particularly painful. I've bluntly worked
>> around it in this example to avoid __proto__ re-wiring.
>>
>> Thoughts?
>
>
> When I enumerate the use cases, I have trouble finding a *good* reason for
> soft-binding. You are correct that it removes a certain amount of
> genericness to use hard-binding, but I think writing a generic function
> should be intentional.
>
>     function foo(bar){
>         bar.call(otherThis);
>     }
>
> If you write a function foo that expects a function parameter bar, and you
> intend to override it's this value, that is a contract, and the |this| you
> substitute also needs to abide by the contract for use inside of the bar
> function. Traditionally, this would be done using a *parameter* to bar,
> instead of changing |this| which seems fragile at best. The only use case I
> see for changing |this| is if the function bar passed in is already an
> existing method.
>
>     //For this use case, why would I ever use an arrow function
>     foo( ()=>this.myBar() );
>
>     //instead of just passing the method directly
>     foo( this.myBar );
>
> I really just don't see the value of changing the |this| value of a function
> created for the purpose of being an argument to a function. And frankly, I
> just don't see many other use cases for arrows. Maybe thats the part I'm
> missing.

Yeah, I think you're missing the composition arguments. If I create
mixins with methods, they're going to have a promiscuious "this" as a
*feature*. You might not write code like this today, but you probably
should ;-)


More information about the es-discuss mailing list