Skip to content

Instantly share code, notes, and snippets.

@getify
Last active December 17, 2020 05:21
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save getify/4596011 to your computer and use it in GitHub Desktop.
Save getify/4596011 to your computer and use it in GitHub Desktop.
A native syntax alternative for `Function.prototype.bind`? This is an open exploration and soft proposal. Feedback appreciated. The main problem I'm trying to solve with `Function.prototype.bind()` is that it creates a new function that's wrapped around the function you specify. This means if you do that a lot, you're creating (and throwing away…
function foo() {
console.log(this.bar);
}
function doSomething(fn, overrideThis) {
if (overrideThis) fn.call(overrideThis);
else fn();
}
var bar = "bar1";
var obj1 = { bar: "bar2" };
var obj2 = { bar: "bar3" };
foo(); // "bar1"
foo.call(obj1); // "bar2";
var foo2 = foo.bind(obj1); // creates a whole new function... can be "ouch" for memory if done a lot
foo2 === foo; // false -- whole new function!
foo2(); // "bar2"
foo2.call(obj2); // "bar2" (not "bar3", `this` can't be overridden here)
doSomething( foo.bind(obj2) ); // "bar3"
doSomething( foo.bind(obj2), obj1 ); // "bar3" (`this` still can't be overriden)
// ATTENTION: new syntax proposal.
var foo3 = foo#obj1; // not a new function, just a special decorated reference to the same function
foo3 === foo; // true... same function!
foo3(); // "bar2" (defaulted `this` to `obj1`)
foo3.call(obj2); // "bar3" (`this` overridable, unlike Function.prototype.bind)
doSomething( foo#obj2 ); // "bar3"
doSomething( foo#obj2, obj1 ); // "bar2" (because `this` is still overridable)
@fearphage
Copy link

That's awkward. They are not and I believe should not be the same function. Identity leads me to believe they are interchangeable.

var a, b = a = 1;
a === b;

Wherever I can use a, I can also use b because they are identical. Your proposal breaks that logic.

@getify
Copy link
Author

getify commented Jan 22, 2013

Actually, that logic is already quite broken by JS the way it stands:

var foo = function() {
   console.log(this.bar);
};
var bar = "bar1";
var myobj = {
   bar: "bar2",
   foo: foo
};

foo === myobj.foo; // true
foo(); // "bar1"
myobj.foo(); // "bar2"

By your definition of "identical" and thus "interchangable", foo and myobj.foo are such. But they certainly don't behave the same simply because they are === to each other. Functions are different animals in JS than are other primitive types, because they can imply different rules for default this binding depending on how they're used.

I'm simply suggesting an extension to the default this binding rules that allows you to express syntactically which this you want as a default.

@fearphage
Copy link

First of all, foo() returns undefined. You want window.bar = 'bar1' to get the results you expect.

In your example, I can clearly see the context of myobj so I know how they are different. This is clear and apparent. I'm aware it can be obscured.

Furthermore if I strip away this context, I still get to use them interchangeably like I expect:

var baz = myobj.foo;
baz() === foo(); // true

@getify
Copy link
Author

getify commented Jan 22, 2013

@fearphage

First of all, foo() returns undefined. You want window.bar = 'bar1' to get the results you expect.

False.

Also, this isn't about return values per se, so your discussion of that is confusing.

I can clearly see the context of myobj so I know how they are different. This is clear and apparent. I'm aware it can be obscured.

This is a fair point. My original intention for this usage was so that you'd use the syntax inline, not pre-assigned as I showed. I tried a simple example to illustrate how it worked, not necessarily to illustrate the best practice.

So, it was to aid in places where you do things like:

// instead of:
$("a").bind("click",myfunc.bind(myobj));

// how about:
$("a").bind("click",myfunc#myobj);

If used in this way, it's equally clear what the intended context would be. Of course, my original example would be what you said, a way that "it can be obscured". With or without my proposal, JS has ways you can be explicit about stuff, and ways you can hide your intentions. Best practice would be to use # in a way that made it clear what was going on.

The purpose of the difference here is two-fold:

  1. no need to create a separate function (which is more memory, more GC eventually, especially in cases where this happens a lot)
  2. If you receive a function reference as a parameter, that you want to override the this for (for whatever reasons), if that reference was defaulted with #obj instead of bind-wrapped with bind(), then you can. If you as a developer WANT to pass around-loosly bound (aka, this-defaulted) functions, so that they can be rebound if necessary, you can. And if you want to seal them so they cannot be rebound, you'd always still have that possibility with Function.prototype.bind().

Furthermore if I strip away this context, I still get to use them interchangeably like I expect

Agreed. Same would be true of the above, strip out the inline #myobj and you stop getting that implicit this-defaulting I'm proposing.

@fearphage
Copy link

Intriguing difference in result. http://jsfiddle.net/C9dvP/ JSFiddle bug?

@fearphage
Copy link

var 
  ,catpants = {/* whatever */}
  ,foo = FUNCTION_DEFINED_ELESWHERE
  ,baz = FUNCTION_DEFINED_ELESWHERE.bind(catpants)
;

foo and baz can not be used interchangeably if you expect the same result. Therefore it is logical to me that foo !== baz. This would be confusing to me and I understand what you want and are trying to accomplish. This would be perplexing and infuriating if I didn't understand this.

assert.strictEqual(thisOne, theOther); // true
assert.strictEqual(thisOne(1), theOther(1)); // if false, cry

I think your concept of managing memory more efficiently is great, but I don't believe this is the correct path to take to fix it. If I can't tell functions are different, I'll have no way to track down why things are broken. How would you fix my example above? What if the functions are passed to you from some other code/api?

@getify
Copy link
Author

getify commented Jan 22, 2013

First off, I think the comparison of two function references is more of a rare type of task in JS. I'm sure there are cases, but I doubt that it happens that often in the overall scheme of things.

I actually don't care whether === would work in the proposal I'm suggesting, but it was the easiest way for me to express that it wasn't actually creating a new function but just a special reference to it. I think the confusion around === in this thread seems a bit overblown, but really, it's secondary to my actual point.

If I can't tell functions are different

How do you tell if two functions are different now? You might open up the console and do a === test, sure. If you only look at the end result, you can't actually be sure of why they are the same or different, and in some cases you can't even be sure if the calling code is accidentally or intentionally overriding your this later, etc.

But the other way, and perfectly valid, is to trace back to where it's assigned. In most cases, you have different function names for diff references, and usually those names are indicative of what the thing is or how it's used. In this case, you'd see either a Function.prototype.bind type of call, or a fn#obj call, and you'd know what its this was being defaulted to.

Actually, I think the #obj style of inlining of binding might be slightly easier to catch than would a .bind() call. But maybe that's just me.

I really don't see the argument that it'd be harder to debug with #obj in there. It'd just be one more thing you'd have to know to be looking for, but it's not any more susceptible to being hidden or confusing than is .bind() usage. And BOTH of them are equally (and infinitely) more explicit than just relying on implicit this bindings.

What if the functions are passed to you from some other code/api?

In either case, whether you are passed a function from some other code/api that was explicitly bound via Function.prototype.bind, or this-defaulted with a #obj on it, you won't be able to tell.

I guess some people like this about JS:

var obj = {
   bar: "bar1",
   foo: function() { console.log(this.foo); }
};

obj.foo(); // "bar1"

function doSomething(fool_me) {
   fool_me();
}
doSomething(obj.foo); // "undefined"

I think it's a bad and confusing thing. To fix that problem, you currently only have Function.prototype.bind() at your disposal.

I'm looking for ways to make it easier for devs to avoid those pitfalls without having the extra memory overhead and without forcing the permanent binding that Function.prototype.bind() results in.

doSomething(foo.bind(obj));
// vs.
doSomething(foo#obj);

For that use case, we get the same result, and I think the same debugability and code maintainability, but foo#obj doesn't create an extra function to be GC'd, and it also passes along a function reference which can still be this overridden if so desired.

@fearphage
Copy link

If foo === bar, I absolutely expect foo(x) === bar(x). Breaking that expectation will be difficult and I'm not sure if the benefit is worth it.

I think passing a function reference is a good idea in some cases. Giving an instance the tool it needs to use when it doesn't necessarily need to be a method on the instance.

Let's try a different path, how are you using this that you need to bind over and over with different contexts?

@getify
Copy link
Author

getify commented Jan 22, 2013

If foo === bar, I absolutely expect foo(x) === bar(x)

If you are saying that you expect functions to all return values, I am confused, because lots of functions do not, and they're not required to in JS. So I don't understand the statement foo(x) === bar(x) which would be comparing return values from the two calls.

If OTOH the spirit of what you are saying is that you expect calling foo(x) to have the same effect as if you had instead called bar(x), then sure, that's one assumption you might make. I don't think it's a particularly useful assumption. But it is AN assumption. It's still orthagonal to my whole point of this gist though.

I added new stuff both to the original gist above, and also to my most recent comment before this one, to try and explain more clearly that this isn't a discussion about reference comparison (as you're focusing on), but about function this bindings and how that's accomplished.

@getify
Copy link
Author

getify commented Jan 22, 2013

To further explain why I am saying this reference discussion is just a side track to the main point...

If we decided that the references should in fact not be === that wouldn't hurt my feelings at all. As I said earlier, I don't really care that much. It's not really part of the proposal, and wouldn't matter one way or the other to me. You could take line 28 out from the above gist, and the rest would be the same.

The REAL point, the thing that matters is, if foo = fn and bar = fn#obj2, even if foo !== bar (fine, whatever), it would be crucial that foo and bar both pointed to the same function, not to a whole new function (more memory, more GC).

@fearphage
Copy link

What I'm saying is if 2 functions are strictly equal, I expect them to have the same return value and side effects. I expect them to be interchangeable, because I've seen that they are in fact the same function.

The problem is your bound function which is identical to the unbound function can't be used interchangeably. That's a problem to me. I understand the goal of this is performance, but you're breaking something very important to the language I feel.

1 function (by identity) can't have N different outcomes (where N is not equal to 1). That's kind of crazy.

Can you tell me about your use case?

@fearphage
Copy link

Didn't see your last comment when I posted. If they aren't equal, then they are different functions logically, right? Is there something that breaks this pattern in JS already (besides NaN)?

Do you have a use case in mind?

@getify
Copy link
Author

getify commented Jan 22, 2013

If they aren't equal, then they are different functions logically, right?

No.

I don't believe the spirit of Function.prototype.bind() is necessarily and absolutely to create a whole new function, though clearly in implementation it does. The spirit of it, as I would explain it, is to bind this (and some other params, if you want) to a function call, to avoid primarily problems where this gets unbound too easily. The fact that JS currently can only do that via a wrapper function is a side-effect of the main spirit of the utility.

So, in that respect, I'm trying to create something else, which has the same spirit (bind some stuff) but which doesn't have that same side effect. That's not to say that the existing utility is bad, just that there's need for this additional thing.

Imagine this:

var obj1 = {
   id: "obj1",
   foo: function(arg) {
      console.log(this.bar,arg);
   }
};
var obj2 = {
   id: "obj2",
   foo: foo
};

obj1.foo(); // "obj1"
obj2.foo(); // "obj2"
obj1.foo == obj2.foo; // true
obj1.foo === obj2.foo; // true

At this point, I think we can both agree that foo as invoked on obj1 is the same function as foo when invoked on obj2.

Similarly:

var obj3 = { id: "obj3" };
obj3.bar = obj1.foo;

obj3.bar(); // "obj3"
obj3.bar == obj1.foo; // true
obj3.bar == obj2.foo; // true
obj3.bar === obj1.foo; // true
obj3.bar === obj2.foo; // true

It's clear these many different invocations (and names) for the function don't change it's identity in any way, right?

So, I argue that no such identity change has happened here:

function thinWrap(fn) {
   return function() { return fn.apply(this,arguments); }
}

var obj4 = { id: "obj4" };
obj4.baz = thinWrap( obj1.foo );

Technically, a new function has been created. But in spirit and intent, I have a pass-thru happening. There's literally nothing this thin wrapper is actually doing. It's almost as if it doesn't exist. But yet the === check will now fail, because it's actually a different function.

That's what I mean by the skew between spirit/intent and practice.

Taking it one step further:

function myBinder(fn,aThis) {
   return function() { return fn.apply(aThis,arguments); }
}

var obj5 = { id: "obj5" };
obj5.bam = myBinder( obj1.foo, obj5 );

At this point, have I changed the spirit of what's going on, so that it's clear that my intent is to get a new function? I say no. The only thing I want is to make sure that I can guarantee what the this will be when it's called, no matter how the function is invoked.

The fact that it's had to create a new function to accomplish that goal is an unintended, orthagonal, irrelevant (to me) side-effect. In fact, it silently creates more memory usage, which COULD be a problem if I'm not aware of what it's doing.

To bring it back to your question... I don't care if obj5.bam !== obj1.foo (nor do I care about obj4.baz !== obj1.foo). Them not being === has no bearing on if I logically treat them as the same function.

Put another way, I don't agree that changing which this is used for a function logically implies anything about creating a new function.

I wouldn't say that fn.call(anotherThis) actually does (or even needs to) create a temporary intermediate function wrapped around fn that has anotherThis bound to it, so that different temporary function can be called (and then discarded!). Changing which this is used doesn't, in my mind, imply changing the function itself (or creating a new one).

@getify
Copy link
Author

getify commented Jan 22, 2013

Do you have a use case in mind?

Yes, I have use cases from my real-world code where BOTH problems I describe here have plagued me.

I had one use case where I had to bind a new event handler every time an event was fired, and that event happened to be a message from a web worker, so it could happen thousands of times in just a few seconds. That web worker couldn't pass along a reference to which object to use, so the only way to fix the problem was to bind the listener so he himself could "remember" which object, later, to use.

Could I have used a closure around a function instead? Yes. But that would have accomplished the same thing with the exact same downside, an extra function being created simply to save a "scope" for later, and then that function being discarded.

For the rebindability concern, it was in a different project entirely, but I had some object methods which I wanted to make sure "always" referred to their own object (because they were being passed around lots in "unsafe" ways, like with setTimeout(), etc). So my solution was literally to create the methods on the object that were already explicitly bound to that object, so they could passed around and regardless of how they were passed around, the correct object this would be used.

This was great until 6 months later, long after the structure of the project was set in stone, that I realized the need to, in a few exception cases, override that binding. Unfortunately, that binding is not overridable in anyway. It's permanent.

What I ended up having to do, which sucked, was keep both copies of the method on the original object, one bound, and one not, and use the appropriate one in the appropriate place.

@juandopazo
Copy link

Are stack traces the only reason for foo.bar === foo#bar? Did I understand correctly?

@angus-c
Copy link

angus-c commented Jan 23, 2013

I like it - it's how bind should have been done.

  1. It fits very well with the call/apply idiom. # sets the default 'this'; call/apply set the exceptional 'this'. All three are treating 'this' as truly dynamic - unlike traditional bind which introduces considerable overhead to avoid something that was there all along with call/apply

  2. Fixes the lost reference issue which is my biggest headache with traditional bind. (i.e. bound function having no reference to orig function)

  3. Never liked hard binding (inc. in new fat arrows). Dynamic 'this' is one of the joys of JavaScript and making call/apply impotent in some functions and not others is confusing

@getify
Copy link
Author

getify commented Jan 23, 2013

@angus-c: good points, thanks. i like how you explained it better than how i explained it. ;-)

@juandopazo: as i said to @fearphage several times, I only said foo.bar === foo#bar to illustrate that there's not a new function being created, not that preserving that reference equality across === would even matter to me. That part I don't care about. The "no extra function wrapper" I do care about. Oh, and the re-bindability is a big thing IMHO.

@juandopazo
Copy link

The fact that I don't like soft bind for most uses doesn't mean I think it shouldn't be in Function.prototype. I don't use soft bind because I like the implicit contract "I will not change the value of this in this function". And that's because of a very common use case: event listeners. It's very common to pass listeners to event emitters which you haven't written yourself. For example:

// I'd like to add a listener and keep |this| pointing to the foo object
obj.addListener('someEvent', foo.listener.bind(foo));

But what if the EventEmmiter is implemented like this?

function EventEmitter() {
  //...
}
EventEmitter.prototype.emit = function () {
  for (var i = 0; i < this.listeners.length; i++) {
    // the author uses apply to pass multiple parameters to the listener
    // and calls apply, breaking my assumption about my listener if it was soft bound
    this.listeners[i].apply(this, arguments);
  }
};

@juandopazo
Copy link

If it isn't a new function, then foo.bar === foo#bar should be true. But in that case we are modifying the original and that can also break expectations. For instance:

var foo = {
  x: 10,
  bar: function () {
    console.log(this.x);
  }
};
var bar2 = foo#bar;
bar2(); // 10, all good
var o = {};
o.bar = foo.bar;
var bar3 = o#bar;
bar2(); // undefined!

It needs to be a new function, even if call/apply can still change this.

@getify
Copy link
Author

getify commented Jan 23, 2013

@juanpodazo my idea, conceptually, was that the #bar part of the reference assignment would add some sort of extra flag to the reference to indicate a default this binding to use when THAT SPECIFIC reference was later used in an invocation. That way the reference itself still pointed to the same underlying function, but the reference also had an extra payload along with it for specifying this.

var obj1 = {
   id: "obj1",
   foo: function() { console.log(this.id); }
};
var id = "X";
var obj2 = { id: "obj2" };
var foo2 = obj1.foo#obj2; // the "#obj2" flag only goes on the foo2 reference itself, not changing the function
foo2(); // "obj2"

var foo3 = foo2;
foo3(); // "X" --> foo2 is the only reference with the extra flag, but foo3 is just a normal reference to the function, not a reference to the reference 'foo2'. therefore, 'foo3' just works in a standard, non-special way. 'foo2' is the only special reference here.

In this respect, I'd be totally fine if the special decorated reference foo2 wasn't strictly === to the unspecial normal reference foo3, even though both point at the same function. The foo2 reference would have this extra flag on him which would make him distinct from foo3. But there'd still only be one underlying function that both of them pointed at.

@juandopazo
Copy link

Ok so you're thinking about references as some sort of "object".I don't know much about VMs and how they work, but how is a "reference object" different from a new function in terms of memory?

I think of variables and references in JS as keys in an object. I conceptualize them as spaces in memory that contain an ID of some sort that points to another piece of memory which contains the object itself. I'd have to check the specification, but I don't think messing with references as such is a good idea. Let's say you can do it. It would mean having either a special kind of reference or a flag in all references. I don't think there's much difference between the two. It would add a runtime cost (to check if it's a specific type or if it has a flag) and/or a memory cost to every reference. That sounds bad.

@getify
Copy link
Author

getify commented Jan 23, 2013

Via @juanpodazo http://wiki.ecmascript.org/doku.php?id=strawman:soft_bind

That's obviously part of what I'm thinking here (in terms of the re-bindability). But my biggest concern is that I don't think we should have to create a whole new wrapper function to express a this binding. Again, my use case was doing LOTS of binding so it's obvious that even small amounts of memory churn/GC from that could quickly add up.

The existing Function.prototype.bind() could be changed to return these "this-defaulted references" (or whatever you want to call them), instead of a whole new function reference. But the obvious legacy breakage side effects would rule that out.

That strawman suggests another API, like Function.prototype.softBind(). While it as proposed doesn't suit my needs, if that new API could instead return these special references that behave as I suggest, there wouldn't be any need for the special # operator jazz.

@getify
Copy link
Author

getify commented Jan 23, 2013

@juanpodazo I suggested a flag on a reference as a way to explain it conceptually. I highly doubt that would influence the implementation at all. In fact, I'm terribly inept at speaking to that, as how VM's work is far beyond my understanding.

It could just be a special kind of reference that refers back to where it was assigned and does a late-binding (examines the object reference at call time to use it). I suspect there are probably other ways too.

Even if it WERE a special kind of reference that had one extra reference on it (so double the memory usage of a reference), that has to be WAY smaller than the memory usage of a full function implementation, right?

Also, just because there's this implementation detail to figure out (I punt because I don't know even remotely how the best way is) regarding how references could remember something like this, doesn't mean that the solution would require the other way around (as you were originally saying) that it would be making a change to the function object itself. That kind of thing is way the other direction from what I'm thinking.

@juandopazo
Copy link

What I meant is that my guess is that they'd have to add a flag for all references, not just references to bound functions, which would impact a lot more than just bound functions. Of course it'd be nice if you could get feedback from someone who writes language implementations.

What I can say is:

  • If there was a way to optimize bindings, I'm sure engine implementors will eventually do it for foo.bind(bar). It looks optimizable up to a certain level because it doesn't have anything in its internal scope, so no scope object, just a copy of the previous function with a different |this| value.
  • Messing with references adds an extra level of complexity to the language. As a developer references are something I don't remotely want to think about.
  • If foo.bar !== foo#bar and typeof foo#bar === 'string' then I'm going to assume it's a new function. Whether it's a new function or a magic reference, that is an implementation detail.

@getify
Copy link
Author

getify commented Jan 24, 2013

@juanpodazo:

If there was a way to optimize bindings, I'm sure engine implementors will eventually do it for foo.bind(bar)

If the VM's were able to transparently make it so a new function wasn't actually being created, that'd be great, but I think it might be problematic in the converse way to how my proposal is being seen as possibly problematic.

For "legacy" reasons (if we can call ourselves in a post-ES5 era yet!), bind() has to return a function reference that's different than the original function, even if the VM is able to go so far as to literally reuse the same function in both instances. Maybe this reference faking/breaking is easy for VM's, maybe not. Not really sure. But could be an issue for them. I'm not sure how much of a perf win, if any, we'd get if the VM still had to create (and later GC) a shell function ("wrapper") just for maintaining these itinerant this bindings.

Moreover, their bind() optimization, even if they worked out that concern, wouldn't, as we've said, address re-bindability, because again for legacy reasons, bind() would have to return non-rebindable refs at the least. softBind would still be necessary.

I would obviously like to see both problems addressed, and if they can be addressed by the same mechanism, I'd consider that preferable.

Messing with references adds an extra level of complexity to the language. As a developer references are something I don't remotely want to think about.

I agree, we shouldn't complicate how references work.

But this would surely be an opaque change to references. It wouldn't be an observable (or mutable) "property" or anything like that. It's just simply a way of preserving with a reference the intended this binding. JS already has several other mechanisms for specifying this, so I'm not sure that adding some rule/mechanism into that mix would really make references across the board any harder for devs to handle.

Whether it's a new function or a magic reference, that is an implementation detail.

Totally agreed. Not sure what you meant by "string" though. Typo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment