class method abstraction

You would be able to call the code below,
for example of a method in return instead of the simple this reference object.

class Example {
  constructor(t) {
    this.text = (t) ? t : 'Hello world!';
  }

  c() {
    console.log(this.text);
  }

  methodOne() {
    this.text = "ONE";
    return this || this.c();
  }

 methodTwo() {
    this.text = "TWO";

    return this || this.c();  
  }
}

const example = new Example();
example.methodOne().methodTwo(); // "TWO";

Proposal: https://github.com/tc39/proposal-`class-method-abstraction`

Hi @joaopjt - welcome!

A few things:

  • Your code example is already valid JavaScript, what it does can’t be changed.
  • the this inside a static method called like that will be a reference to the class constructor itself. So it can’t be used to call instance methods such as query, there should be an instance of the class
  • it is not clear what the problem is you are trying to solve, could you go into more detail?

Hi @aclaymore, thanks for the welcome. I've made some changes in the code to really show what the trouble we getting in without the proper abstraction of the class methods.

I guess now will be enought and quite clear to remember what we dealing at without the right abstraction of the methods.

Thanks for now :)

btw - your proposal link seems to be broken (it gives me a 404 error).

Could you expound on why you're expecting the last line to log out "TWO"? The modified code example already runs without errors, and won't log anything out, because this.c() never gets called. (The return this || this.c(); lines cause this to simply be returned, since this is truthy). Are you wanting the return this || this.c(); lines to behave differently? If so, how and why?

I'm waiting the call of this.c() on the second call method, so it would be a abstracted method.

Are you saying

//          v---- should return `this` 
//                      v---- should return `this.c()`
example.methodOne().methodTwo(); // "TWO";

based on, I'm guessing, where the method calls are in the chain?

Another possible example

//          v---- should return `this` 
//                      v---- should return `this` 
//                                  v---- should return `this.c()`
example.methodOne().methodTwo().methodOne() // "ONE";

?

Yes. Imagine a Query Builder. So we have the following call:

DB.select('time').from('space');

Instead of:

DB.select('time').from('space').query();

What your thoughts on the subject?

A challenge here is that while it may look clear what should happen when we look at the code. There needs to be clear semantics for how this would work, and right now there is no mechanism for functions to change their behavior based on the 'position' of where they were called. The operation is apply and that does not pass any information other than the receiver and arguments. So:

example.methodOne().methodTwo();

Is the same as doing:

const $1 = example;
const $2 = Reflect.get($1, "methodOne");
const $3 = Reflect.apply($2, $1, []);
const $4 = Reflect.get($3, "methodTwo");
const $5 = Reflect.apply($4, $3, []); 

At a first moment, it should return the this reference, else way it call the method. Is matter of abstraction in game here.

We shouldn't let it fall easily like that, I really recommend the pull request of the feature as soon as possible.

Is too a matter of applying arithmethics to the code, not just the abstraction

Functions can’t, by design, behave differently based on where they’re called, except for the receiver. Making a function that works differently in a chain then not is imo a nonstarter.

This is an interesting problem space. I don't know if we'll be able to find any great solutions for it, but I'll at least like to take a step in, what I presume to be, a more acceptable direction.

For a syntax like this to be work-able, I think we'd need some sort of syntactic token to state that a chain is starting.

e.g. something along the lines of


class MyThing {
  data = [];
  f() {
    this.data.push('F');
    return this;
  }
  g() {
    this.data.push('G');
    return this;
  }
  [Symbol.chainEnd]() {
    console.log(this.data);
  }
}

const thing = new MyThing();
(chain thing).f().g(); // ['F', 'G']

Where chain will cause the Symbol.chainEnd() function to be called on its argument after the dot-chain ends.

This still feels like a messy solution, but at least it's a plausible solution.

Amazing. Thanks for the answer, it will help a lot!

I would like to link Dart's cascade notation. Essentially, Dart allows for this:

example
    ..methodOne()
    ..methodTwo()
    ..methodOne()

And the previous method never needs to return this.

1 Like