Scoped binding of a method to an object

Brendan Eich brendan at mozilla.com
Tue Oct 15 11:25:34 PDT 2013


> Allen Wirfs-Brock <mailto:allen at wirfs-brock.com>
> October 14, 2013 6:09 PM
>
> Speaking from the perspective of someone whose probably has permanent 
> OO brain damage, it doesn't do a lot for me.
>
> The reason I "invoke a method" on an object is because I want to do 
> polymorphic dispatch on the method name.
> myArray::shuffle() doesn't do that for me. No polymorphic dispatch. If 
> myArray actually does have a shuffle method it isn't called. If that 
> sort of direct function invocation is what I want, I'll just code a 
> function call. No method invocation syntax and no |this| value is need.

Not so in the case of Russell's sketched "underscore2" (note the 2). 
Underscore (http://underscorejs.org/) provides FP-style APIs hung off a 
_ object, e.g. _.map. Russell's myArray::shuffle() using a shuffle 
imported from underscore2 is clearly using an OO-style API from a 
made-up OO-style "underscore2".

I hope this is clear but fear it is not. I know it's not your main point 
-- that there's no "fall-forward" on Array.prototype.shuffle -- with 
which I agree. But it gets to why |this| matters.

> I still don't get why so many JS programmer with a FP orientation want 
> to do things with the |this| binding. |this| is for us OO geeks, if 
> you are doing FP you don't need it. If you want to write shuffle, 
> each, and filter functions just code them as normal functions passing 
> the collection as the first argument.

That would be what underscore offers today, perhaps with better exports 
in an ES6 version (import {map, reduce} from "underscore-mod", no need 
for _.map, just call map as a function).

> If I want to use you functions as a methods on one of my objects I'll 
> just code something like:
> class {
> shuffle() {return shuffle(this)}
> }

Why would you both writing such boilerplate if you did not have to?

Assume that the problem of monkeypatching is solved somehow. Then two 
problems remain at play:

P1. How to select an extension in a method (not function) call, in a 
large-ish extent of code where one may want to select a non-extension 
too (on any type of object).

P2. How to "fall forward" when the extension is not needed because the 
built-in has the method.

> Now, what might be useful would be :: that has approximately this 
> semantics
>
> import {shuffle,each,filter} from "underscore2";
> myArray::shuffle();
>
> desugars as
>
> do {let _method = myArray[shuffle.name];
> _method ? _method : shuffle}.call(myArray);
>
> in other words, if myArray has a 'shuffle' method, call it; otherwise 
> call the default shuffle method that I'm providing.

SOE 
(http://wiki.ecmascript.org/doku.php?id=strawman:scoped_object_extensions) 
does this differently, by adding an extension object (associated with a 
prototype object) to a lexical scope, such that in that lexical scope, 
using an object that delegates to the extended prototype will use the 
extension, always. It will not "fall forward" to prefer a property from 
the object itself.

Quoting from 
http://wiki.ecmascript.org/doku.php?id=strawman:scoped_object_extensions#property_lookup_spec_changes:

Section 8.12.1 [[GetOwnProperty]](P) is modified as follows:

When the [[GetOwnProperty]] internal method of O is called with property 
name P and lexical scope L, the following steps are taken:

 1.
    Let D be the result of calling [[GetExtensionProperty]] on object O
    with property name P and lexical scope L.
 2.
    If D is not undefined, return D.
 3.
    Else return [[GetUnextendedOwnProperty]] on object O with property
    name P.

When the [[GetExtensionProperty]] internal method of O is called with 
property name P and lexical scope L, the following steps are taken:

 1.
    If O doesn’t have an object extension in lexical scope L return
    undefined.
 2.
    Else let E be the object extension for O in lexical scope L.
 3.
    Return [[GetUnextendedOwnProperty]] with object E and property name P.


Ok, so maybe this lack of a (P2) solution is just an SOE design choice 
with which you disagree, but we need to be clear. Do we want fallback, 
or fall-forward, or neither? SOE was intended to provide an alternative 
to monkey-patching, with fallback when the named property is accessed on 
an unextended object. This is not the same as fall-forward, AKA "object 
detection".

Here I smell more "DWIM". People do not mean the same thing when they 
write (just the expression) myArray.shuffle(). The meaning depends, as 
you say, at least on polymorphic dispatch via the prototype chain in JS 
today. We could add more potential kinds of meaning, but TC39ers 
rejected adding a semi-static or dynamic scoped lookup on right of dot, 
per SOE.

What's left in my view is a combination of (a) monkey-patching as we 
know it (object detection is orthogonal and doable), or (b) some new 
operator that enables explicit "here is what I mean; do it" gesturing, 
or (c) the hypothetical, perhaps infeasible, static-only resolution 
system Andreas posed as a revived SOE requirement.

It so happens that the bind operator -- a solution of the (b) kind from 
last paragraph -- already solves problem (P1) above. That it does not 
solve (P2) is not a flaw in the bind operator. Again, SOE does not solve 
(P2)!

/be



More information about the es-discuss mailing list