Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
icholy opened this issue Apr 27, 2018 · 16 comments
Open

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

icholy opened this issue Apr 27, 2018 · 16 comments

Comments

@icholy
Copy link

icholy commented Apr 27, 2018

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 icholy changed the title Can't we just have simple object::method binding? Can't we just have a simple object::method binding syntax? Apr 27, 2018
@ljharb
Copy link
Member

ljharb commented Apr 27, 2018

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.

@WebReflection
Copy link

WebReflection commented Apr 27, 2018

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.

@zenparsing
Copy link
Member

@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.

@tiansh
Copy link

tiansh commented Aug 3, 2018

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.

@WebReflection
Copy link

@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.

@tiansh
Copy link

tiansh commented Aug 3, 2018

@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.

@WebReflection
Copy link

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.

@tiansh
Copy link

tiansh commented Aug 3, 2018

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.

@zenparsing
Copy link
Member

@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

@ExE-Boss
Copy link

ExE-Boss commented Aug 7, 2019

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.

@icholy
Copy link
Author

icholy commented Aug 7, 2019

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];
};

@ljharb
Copy link
Member

ljharb commented Aug 7, 2019

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

@icholy
Copy link
Author

icholy commented Aug 7, 2019

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

@ljharb
Copy link
Member

ljharb commented Aug 7, 2019

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

@WebReflection
Copy link

the solution for equality was already posted here and it's still not difficult:
#54 (comment)

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

@Fenzland
Copy link

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};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants