zenparsing / es-abstract-refs

Abstract References Proposal for ECMAScript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

base bound to this in Function.prototype[Symbol.referenceGet]?

mariusGundersen opened this issue · comments

Shouldn't the result of Function.prototype[Symbol.referenceGet] be bound to the base, as in:

Function.prototype[Symbol.referenceGet] = function(base) { return this.bind(base) };

If I understand it correctly, that would allow:

function take(count){
  var result = [];
  for(let i=0; i<count; i++){
    result.push(this[i]);
  }
  return result;
}

var list = [0,1,3,4,5,6,7,8,9];
list::take(3);//shuold return [0,1,2]

I just caught that too - you beat me by a few minutes. ^_^

As written, I don't understand how the base value makes its way into functions by default. I agree that it looks like the default impl should bind base to the function.

Thanks @mariusGundersen and @tabatkins !

Actually, returning a function bound to base isn't necessary, because of the way that references work in ES. If you look at the runtime semantics for function calls:

You'll see that the value of the reference is pulled out (12.3.4.1 step 2) and then later on the base value is pulled out (step 5.a.i) and used to execute the function (step 9).

So in our case:

list::take(3);

We have:

  • GetValue applied to our reference: take (a function)
  • base: list

And step 9 more-or-less does take.call(list, 3).

So it just works. Agreed?

Beyond that, I suppose you could argue that :: should bind the function anyway, but I don't think it's particularly beneficial. If you actually want a bound function and you already have an identifier reference, then all you have to do is:

var bound = someFn.bind(obj);

which is almost as good as a hypothetical:

// Pretending the "::" gets bound functions
var bound = obj::someFn;

Right. Something I didn't realize was that this is also true for getters (at least in firefox). If you return a function from a getter the function will have it's this value set to the object, even without bind. Since this proposal works the same way as getters, I agree that it should return an unbound value.

var obj = {
  get take(){
    return function take(count){
      return "taken "+count+" from "+this;
    }
  },
  toString: function(){
    return "some list";
  }
}

obj.take(3) //taken 3 from some list

Hmm, that's some pretty bizarre ES behavior that I didn't know about, @mariusGundersen.

@zenparsing

If I am understanding this correctly, it seems like you've encoded the "binding" magic directly into the definition of the :: operator. That is, in x::y, the result is essentially "y bound to x" in abstract spec terms (i.e., a reference with referenced name y and base x). This seems unfortunate, as you're smuggling in a tuple by reusing two concept that have some pretty well-established meanings with regard to property access.

My (incorrect) initial impression of how this worked was that x::y created a new abstract (x, y) tuple, that was interpreted as either y[Symbol.referenceGet](x) or y[Symbol.referenceSet(x, ...) or y[Symbol.referenceDelete](x) depending on the situation. Then, all of the behavior would lie entirely within the definitions of y[Symbol.reference*]. If that were the case, defining Function.prototype[Symbol.referenceGet] to do the binding would be necessary, since there is no binding happening automatically, as it's a generic tuple and doesn't have the base/reference name semantics built in. This seems preferable since it leaves less implicit and makes this less about "binding" (albeit at an abstract spec level) and more about a generic tuple operator.

The other thing is, it kind of sucks to have x::y mean y, whereas x::y() means y.call(x). That is why the binding behavior is attractive, since it is more self-consistent. Yes, it only saves five characters---but that's not the point; consistency is.

@domenic A better name for the proposal might have been "Virtual Properties", because what we're doing here is treating some arbitrary thing (like a function or a (Weak)Map) as if it were a real property of the base object.

So from that point of view, the behavior of :: is consistent with ..

To illustrate, if I write this:

x.y(z);

It's evaluated something like this:

x[[Get]](y).call(x, z);

And the behavior of proxies makes that observable.

The :: operator flips it around in a sense:

x::y(z);
// Evaluated something like:
y[Symbol.referenceGet](x).call(x, z);

But the [[Get]]/[[Call]] dance remains the same.

Also, consider a "private field" that stores a function. I believe this should work:

const _field = new WeakMap;

let obj = { name: "zen" };
obj::_field = function() { return this.name };
console.log(obj::_field()); // Logs "zen"

It's actually not that weird @domenic. A JavaScript function's this value is whatever is in front of the dot in the expression x.y() (in simple terms), so

var list = {
  take: function (count) {
    return 'take ' + count + ' from ' + this;
  }
};
list.take(3); //take 3 from [object Object]
var take = list.take;
take(3); //take 3 from [object Window]

This will also be true for the :: operator, so

list::take(3) //will return 3 items from the list
let takeFromList = list::take;
takeFromList(3) //will return 3 items from the global object (the window)

@mariusGundersen thanks for that.

In general I guess I am starting to feel that this proposal is a bad fit for a bind operator, and for private state :(. Too many rough edges for both compared to separate proposals that are more ergonomic for their designated use case. E.g. it makes sense that list::take doesn't bind if you think of this as a virtual property operator, but that loses one of the main benefits of the bind operator.

I'm not sure when I would use the Symbol.referenceSet and Symbol.referenceDelete, but Symbol.referenceGet is really useful for getting extension methods into ES. For extension methods we don't need a bind operator, we really only need a call or an apply operator. This proposal gives us that, plus some other stuff. I don't really see any use for the other stuff right now, but I haven't really tried to find any either.

@domenic We've identified that this proposal doesn't provide sugar over Function.prototype.bind, and method extraction in particular.

In my opinion though, it covers the most important use cases for the "function bind operator" proposal. We're basically just left with method extraction.

Is method extraction important enough to warrant it's own sugar? My intuition is that, between arrow functions and this proposal, the answer is no. But that's something we should certainly consider.

See #5 for open issue