tc39 / proposal-bind-operator

This-Binding Syntax for ECMAScript
1.75k stars 30 forks source link

Virtual properties (accessors) as well as virtual methods #33

Open andyearnshaw opened 8 years ago

andyearnshaw commented 8 years ago

I'm a huge fan of this proposal, I'd really like to see it advance to Stage 1 and beyond. I'd also like to get some thoughts on providing virtual accessors as well as virtual methods. For example, lets assume some kind of Reflect.createLooseAccessor() function:

const
    wm = new WeakMap(),
    data = Reflect.createLooseAccessor({
        get: () => wm.get(this),
        set: (v) => wm.set(this, v)
    });

let foo = document.getElementById('foo');
foo::data = 'bar';
console.log(foo::data);
// -> "bar"

This reminds me of jQuery's data(), but doesn't change the target object, making it possible to use it on immutable objects. Another real-world example:

import { should } from 'chai';

const foo = {};
test('A test', function () {
    foo.bar::should.be.a('function');
});

Chai's should sits on Object.prototype is generally a bad idea if the value being tested is potentially undefined or has no [[Prototype]] (e.g. an object created with Object.create(null)). As a virtual accessor, it doesn't infect anything and can be used on any value type.

I realise there are significant conflicts with the current proposal (such as foo::data behaving differently for a virtual accessor), but my goal is just to spark a discussion about it. I also realise that esdiscuss might be a better place to raise this, but thought it a good idea to get some feedback from proponents of this proposal first.

Alxandr commented 8 years ago

WRT Chai's should (imho) that is rather just implemented as a function. It'll behave (mostly) like C#'s extension methods, in that you could do foo.bar::should().be.a('function');. No need for prototype modifications, nor changing this proposal at all.

andyearnshaw commented 8 years ago

Yeah, I thought that would be one of the first things mentioned 😉 The same could be said of my first example, that foo::data('bar') could be preferred to foo::data = 'bar'. I think it's the aesthetics of virtual accessors I like the most, it looks more natural.

zloirock commented 8 years ago

It's the most interesting part of abstract references which we lost after withdrawing this proposal.

https://github.com/zenparsing/es-abstract-refs https://github.com/zenparsing/es-abstract-refs/issues/12#issuecomment-76418860

Igorbek commented 8 years ago

That wouldn't work, because of ambiguity being impossible to resolve. Say we have this:

const fn = { a: () => 1 };
const prop = createAccessor({
  get: function () { return ({ a: () => 2 }); }
});

x::fn.a() // 1
// desugars to
fn.a.call(x);

// on the other hand,
// if we desugared this
x::prop
// to
prop.get.call(x)
// then would this
x::prop.a()
// be desugared to
prop.get.call(x).a() // ? is that what you wanted?
// however, if align with prev example, we'll get this
prop.a.call(x)
benjamingr commented 8 years ago

Note that we can get close with a proxy:

const datas = new WeakMap();
function data() {
  let map = datas.get(this);
  if(!map) {
     map = new Map();
     datas.set(this, map)
  }
  return new Proxy(this, {
    set(target, prop, value, receiver) {
        map.set(prop, value);
        return value;
    },
    get(target, prop, receiver) {
      return map.get(prop);
    }
  });
};
var obj = {};
obj::data.x = 15;
obj.x; // undefined
obj::data.x; // 15
Igorbek commented 8 years ago

I was pointing that it would be impossible to disambiguate:

obj::data.fn();

whether it is a binded property accessor data that returns { fn: () => { ... } } or it is a data = { fn: () => { ... } } so that data.fn is actually being binded.

Igorbek commented 8 years ago

or more clear, with precedence in place:

obj::data.fn() == (obj::(data.fn))(); // '.' has higher precedence that '::', as it stands now
obj::data.fn() == (obj::data).fn();   // '::' would have priority

And note, latter is highly undesirable.

WebReflection commented 8 years ago

FWIW agreed '::' should have lower priority than '.' or it would lose most of its use cases, first of all: borrowing methods

Needing a reference upfront or parenthesis to explicit make it lower priority is undesired indeed.

On Friday, 15 July 2016, Igor Oleinikov notifications@github.com wrote:

or more clear, with precedence in place:

obj::data.fn() == (obj::(data.fn))(); // '.' has higher precedence that '::', as it stands now obj::data.fn() == (obj::data).fn(); // '::' would have priority

And note, latter is highly undesirable.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/zenparsing/es-function-bind/issues/33#issuecomment-233080567, or mute the thread https://github.com/notifications/unsubscribe-auth/AAFO9Qag8CZgAFUX9UKeFE3QEUt8NYheks5qWAOngaJpZM4Ib43M .

dead-claudia commented 7 years ago

This sounds like something better suited to suggest in the private fields repo. I don't think any suggestion here would gain much traction.