proposal-bind-operator icon indicating copy to clipboard operation
proposal-bind-operator copied to clipboard

Can't we just have a simple object::method binding syntax?

Open icholy opened this issue 7 years ago • 16 comments

Why are you trying to make a simple thing complicated? All we need is sugar to:

// convert this
this.service.method.bind(this.service)

// into this
this.service::method

Drop the pipeline non-sense.

icholy avatar Apr 27 '18 18:04 icholy

They achieve different purposes; the bind operator has 3 use cases, 2 of which pipeline solves - method extraction is the one it doesn't solve, which will need to be addressed somehow.

ljharb avatar Apr 27 '18 21:04 ljharb

obj::method === obj::method is also an unsolved need of the community, that keeps adding listeners via bind or, in the future, pipe operator.

If only obj::method could translate into the following bound(obj, method) that'd be awesome.

const wm = new WeakMap;
const create = obj => {
  const map = new Map;
  wm.set(obj, map);
  return map;
};
const set = (obj, method) => {
  const map = wm.get(obj) || create(obj);
  const bound = method.bind(obj);
  map.set(method, bound);
  return bound;
};
const bound = (obj, method) => {
  const map = wm.get(obj);
  return map && map.get(method) || set(obj, method);
};

So that bound(obj, method) === bound(obj, method) would be true.

WebReflection avatar Apr 27 '18 22:04 WebReflection

@WebReflection I agree we need to address this pain point.

Syntactically speaking, binary :: probably won't work, since we will likely want to support method extraction for computed property names. We might be able to come up with a reasonable prefix operator though.

zenparsing avatar Apr 28 '18 13:04 zenparsing

Nothing different here with following python code:

class A:
    def f(): pass

a = A()
print(a.f is a.f) # False

I don't think there are any importance for obj::method === obj::method.

tiansh avatar Aug 03 '18 05:08 tiansh

@tiansh I guess that's because you've never added a listener you want remove later on

obj.addEventListener('type', obj::method);

That is why is important, many developers write obj.addEventListener('type', obj.method.bind(obj)); thinking they can remove that later on.

I've solved this via {handleEvent()} trap but not everyone knows it and it doesn't work in NodeJS Emitter.


About the pipeline VS method extraction, I've created an utility that does something similar to this:

Function.prototype.this = function () {
  return self => this.apply(self, arguments);
};

That plays super nicely with pipeline operator:

const {map, sort} = Array.prototype;
const names = document.querySelectorAll('*')
                |> map.this(el => el.nodeName)
                |> sort.this();

If that could be proposed in core, it'd be awesome.

WebReflection avatar Aug 03 '18 10:08 WebReflection

@WebReflection What you need is just saving it to a variable for further reference. Nothing special here. And that is the most understandable way. Nothing magic here. It at least follow the traditional of this language. And do not enviove too many things to make it complex, which should also be new learner friendly.

tiansh avatar Aug 03 '18 10:08 tiansh

You really don't have to explain me anything, developers keep doing that mistake, not me, never me. I use handleEvent or cached listeners already.

WebReflection avatar Aug 03 '18 10:08 WebReflection

Yes, that’s because bad api design. Not by language. And this (use a uncatched right value for removeEventListener) can be easily detected by browsers and linters. But not by language.

tiansh avatar Aug 03 '18 10:08 tiansh

@tiansh In python, bound methods do not have object identity but they do have equality, which makes a difference:

class A:
  def f(): pass

a = A()
d = dict([(a.f, 1)])
a.f in d # True

zenparsing avatar Aug 03 '18 15:08 zenparsing

This would be similar to the Java 8 method reference syntax, which I consider a plus:

Consumer<String> println = System.out::println;
println.accept("Some string");

// Equivalent to: (Anonymous object implementation)
var println = new Consumer<>() {
	public void accept(String string) {
		System.out.println(string);
	}
};

// And to: (Java 8 lambda syntax)
Consumer<String> println = string -> System.out.println(string);

Uses the Consumer<T> functional interface as an example.

ExE-Boss avatar Aug 07 '19 01:08 ExE-Boss

Having obj::method === obj::method doesn't seem very difficult.

const sym = Symbol("extracted");

Object.prototype.extract = function (method) {
	if (!this[sym]) {
		this[sym] = new WeakMap();
	}
	if (!this[sym][method]) {
		this[sym][method] = method.bind(this);
	}
	return this[sym][method];
};

icholy avatar Aug 07 '19 03:08 icholy

Nothing new can be added to Object.prototype; and that wouldn’t address null objects anyways.

ljharb avatar Aug 07 '19 04:08 ljharb

@ljharb this method is supposed to represent the :: operator's behaviour. I'm not suggesting actually adding an extract method.

icholy avatar Aug 07 '19 13:08 icholy

in that case, storing the cache observably and mutably on the object - which might be frozen anyways - is a nonstarter.

ljharb avatar Aug 07 '19 17:08 ljharb

the solution for equality was already posted here and it's still not difficult: https://github.com/tc39/proposal-bind-operator/issues/54#issuecomment-385111531

this proposal is going nowhere anyway, so I think it could be closed?

WebReflection avatar Aug 08 '19 13:08 WebReflection

That's hitting the real pain point. Binding a method from third part library can be substituted by |>. What we really need is to bind methods from prototype of the object itself.

There is a plan to mix them together.

function addClass( className ){ this.classList.add( className ); }

const foo= document.getElementById( 'foo' );

const setAttributeToFoo= foo::setAttribute;
const removeAttributeFromFoo= foo::['removeAttribute'];
const addClassToFoo= foo::{addClass};

Fenzland avatar Nov 27 '19 03:11 Fenzland