Syntax to get same object that method was called on (Easy method chaining)

# Edwin Reynoso (9 years ago)

Could we get a way to basically to get the object back from after a method was called, so that instead of this:

let obj = {
 doSomething() {
   // some side effect
   return 5;
 },
 doSomething2() {
   // some other side effect
   return {x: 5};
 }
}

obj.doSomething();
obj.doSomething2();

We could do this:

obj.doSomething()#doSomething2();

Where # gets the object that the method was called on (basically the this value of the method call)

Why?

There are lots of methods that don't return anything (they return undefined by default) and instead of retyping the same object we could have # give us the object back. There's also methods that do return something but I may not want that value:

let arr = [1,2,3];
arr.push(4).forEach(function() {...}); // throws because the push method
returns the length of the array

With # I could do:

arr.push(4)#forEach(function() {...});

Ayy even forEach itself doesn't return, there's a discussion on changing that instead of breaking APIs which we can't we can have this:

arr.push(4)#forEach(function() {...});

which won't require any API changes, and could be used on any function.

Now I have no idea about implementation details, so not sure if this is possible

thoughts?

# Jordan Harband (9 years ago)

This immediately raises some questions for me:

  • what would foo()# return? (a bare function call - the question applies to strict mode, and sloppy mode)
  • what would foo.bar.call(baz)# return? (foo? or baz?)
  • what would baz.quux = foo.bind(bar); baz.quux()# return? (baz? or bar?)
  • what would [].length# return? (an accessor property) (throw? the array? undefined?)
  • what would { foo: 3 }.foo# return? (a data property) (throw? the object? undefined?)
# Edwin Reynoso (9 years ago)

foo()# this one it depends, how foo is defined

if foo is defined in the window scope, it'll return the window:

if not and just like this in another scope, it should most likely throw:

(function() {
 function foo() {};
 foo()# // throws
})();

foo.bar.call(baz)# would return baz because

(basically the this value of the method call)

baz.quux = foo.bind(bar), that's something that I'm not sure what's best, so not sure, but because of what I said above I think the right answer would be to return bar (the this value)

[].length# will return the array

{ foo: 3 }.foo# same thing as above, it'll return the object

# Caitlin Potter (9 years ago)

I think, if you were going to have this, it would be best if bound this were ignored, and if it were a syntax error for non-property references.

Reason being, you'd actually be able to tell what was going on, mostly, by reading the source code. Otherwise it's really hard to tell what's going on.

A more interesting question could be super properties, since the appropriate receiver isn't the same as the object the method is looked up on

super
  .foo()
  .bar(); // overridden bar or superclass bar?

Dart has something like this, do they have weird rules about this to work around too?

# Eric Suen (9 years ago)

with

obj.(doSomething(), doSomething2());

you don’t need to introduce new operator.

# Edwin Reynoso (9 years ago)

That's not possible @

# Salvador de la Puente González (9 years ago)

If that exists I think it should be the context object the function is called with. So foo()# is simply undefined in strict mode. El 26/10/2015 7:48, "Edwin Reynoso" <eorroe at gmail.com> escribió:

# Michał Wadas (9 years ago)

Why? It isn't valid syntax now so it can't be misunderstood by parser.

# Adam Ahmed (9 years ago)

Hello, it's me, long-time lurker and person whose opinion shouldn't matter.

I find the syntax and semantics of this to be nothing I've ever had a use case for, and I think it would make the language more complicated without much benefit.

But if JS really wanted something like this, I'd prefer to bring back with() in strict mode with some better syntax and semantics. No confusing fallback to the outer scope, and hopefully syntax that avoids the question of where a value comes from (e.g., requiring a leading dot or square brackets).

with(obj) { .doSomething(); .doSomething2();

.boom(); // TypeError obj.boom is undefined

.returnsTwo()
.toString(); // no ASI needed, makes it "2"

["returnsTwo"]() // 2

[] // invalid statement

.a = 42;

} obj.a // 42

But even then I'm not sure about this general idea. And don't see much readability benefit in most cases.

Thanks for listening.

Adam On Oct 26, 2015 7:59 PM, "Salvador de la Puente González" < salva at unoyunodiez.com> wrote:

If that exists I think it should be the context object the function is called with. So foo()# is simply undefined in strict mode. El 26/10/2015 7:48, "Edwin Reynoso" <eorroe at gmail.com> escribió:

# Eric Suen (9 years ago)

What I means is obj.(f1() , f2()) may better than obj.f1()#f2(), thought your syntax is shorter.

  1. it does not need new operator.
  2. is comment in some other languages

  3. if you want result, you can do with obj.(a = f1(), b = f2()) or [a,b] = obj.(f1(), f2())

"Edwin Reynoso" <eorroe at gmail.com>

That's not possible @Eric

On Mon, Oct 26, 2015 at 2:20 AM, Eric Suen <eric.suen.tech at gmail.com> wrote:

with

obj.(doSomething(), doSomething2());

you don’t need to introduce new operator.

# Erik Arvidsson (9 years ago)

There used to be a proposal in the ES5 timeframe for cascade expressions. This is what later made it into Dart using the .. syntax. For ES the syntax was using .{. The proposal never gained a lot of traction so it was removed.

google/traceur-compiler#405

# Bergi (9 years ago)

foo.bar.call(baz)# would return baz because

(basically the this value of the method call)

That makes no sense. The method that is called here is call, and its this value is foo.bar, which should be returned. If I get it right, you want to extend the property accessor syntax, not the function call one.

, Bergi

# Bob Myers (9 years ago)

I love cool syntactic innovations as much as the next guy. I'm sure there are a bunch sitting out there waiting to be discovered that will make us all sit back in awe.

But it's not a particularly new insight to observe that especially if they introduce no new basic functionality and are just sugar, then such innovations have to solve a real pain point, not have simple user-side solutions, and not eat too much into the syntactic space for better future ideas.

Here, I'm really having a hard time trying to figure out what's so painful about

obj.foo();
obj.bar();

that would lead us to introduce .{ or .. or #. to solve the problem of typing obj twice. The resulting code (whether obj.foo()..bar() or obj.{foo(); bar()} or obj.foo()#.bar()) is neither more writable, nor more readable, nor more maintainable, nor more provably correct. On the contrary. All are more obtuse, obscure, and bug-prone.

As a semi-ridiculous example of a seemingly useful new syntactic structure, let me introduce the <#> syntax. This is a mechanism for writing an

expression, enclosed in <>, which throws away its value and instead

evaluates to a value within the expression prefixed by a (tightly-bound) # operator. So, for instance, I can write

return <foo(#5)>;

In other words, call foo(5), but deem the expression to evaluate to 5 and return that.

The proposed obj.foo()#.bar() could be written as

<#obj.foo()>.bar();

I have no trouble imagining that this syntax could result in some more compact code, but I doubt if it would be more writable or readable. It's syntactic over-think.

Another criteria for accepting new syntax ideas is that they should be general and compositional. In other words, they should solve more than one problem. Solving the single problem of "take a method call on an object and fix it up so that later stuff in the expression refers to the object instead of the result of the method call" does not meet this criteria.

I mean, if you want to write chainable functions, then just write them that way.

If you have non-chainable functions, and you want to make them chainable/cascadable, it's not a real problem to chainify a function:

function chainify(fn) {
  return function() {
    void fn.apply(this, arguments);
    return this;
  };
}

obj.doSomethingChained = chainify(obj.doSomething);
obj.doSomethingChained().doSomething2();

If you have a whole set of APIs you want to chainify, you could take a leaf from the promisifyAll paradigm, and do it all at once:

chainifyAll(obj);

which would create methods xxxChained for all methods on the object.

-- Bob

# Edwin Reynoso (9 years ago)

@CaitIin see what your saying which would simplify things:

foo()# returns undefined foo.bar.call(baz)# returns foo (correct?) baz.quux = foo.bind(bar) returns baz (correct?)

and as for property accessors I think they should throw, it should only be for function calls, because why after getting a property would you want to get the object back? The only reason I can think of is that its a getter and it does have a side effect (which in my opinion its just a bad idea)

Take [].length why would you actually type out .length if you don't want it, compare to a function call which it should have a side effect.

So these should throw:

[].length# // throws ({ foo: 3 }).foo# // throws

@Adam

I'm not sure about that, not sure if with() would be brought back to "good use" (I kind of always liked it) but interesting thought

BTW I don't think you need the preceding . in with statements

@Eric

Other languages having # as comments, doesn't matter

I don't really like that syntax because this is how it would look if you had it in different lines, but that's not really up to me:

obj.(
  f1(),
  f2()
);

Compare that to:

obj
 .f1()
 #f2()

There's extra parentheses and having a comma, also there's already a comma operator, not sure if that will confuse things.

@Erik

That's a nice syntax I suppose

@Bergi

No, the method being called is actually bar, but using call calls the method bar with a different this value.

@Bob

I see what your saying that's actually an idea I had first, just not that syntax, but that's true if the APIs are ones you wrote, there are lots of APIs that are out there already and may have functions that return other values that you may or may not need.

Yea you could use that chainify function easily if you were the one writing the API, but like I said above, there are methods already that may return something, and what they return is useful, but sometimes you may not need them. They could have a parameter as to what to return but that just complicates things, having a way to do this in the languages means 2 things:

  1. No API methods have to be changed, to do accomplish this
  2. APIs can then return something else, and JS can always provide a way to return the object back, instead of the API having to do it.
# Caitlin Potter (9 years ago)

More like

Foo()# // SyntaxError Foo.bar()# // Foo Foo.bar.baz()# // Foo.bar

The super property thing is a harder problem. They would always return this i guess, but I'm not sure if chaining a super method should lookup a method in the prototype first, or instance first

# Edwin Reynoso (9 years ago)

@Caitlin yea that's correct, but you didn't compare what I had typed out:

foo()# returns undefined

foo.bar.call(baz)# returns foo (correct?)

baz.quux = foo.bind(bar) returns baz (correct?)

@Erik

but what happens when a method returns a Number

5..toString(); // works in ES5 because the first . is for the decimal

# Caitlin Potter (9 years ago)

foo()# would best throw an early error — theres no syntactically apparent this, so cascading doesn’t add anything useful.

foo.bar.call(baz)# would return foo.bar, as the method being invoked is call, and foo.bar is the this for that method.

baz.quux = foo.bind(bar); baz.quux()# would return baz, yes.

But, here’s the issue — it still doesn’t deal with callable objects with a lexical this, like arrow functions — and of course it doesn’t help much with bound functions. So, you can get misleading results with the cascading operator in JS, even in this dramatically simplified form.

I don’t really expect that this proposal (with these gotchas) would be able to reach consensus and make it into a ratified specification.

# Isiah Meadows (9 years ago)

I think this could be implemented with no regard to the function implementation, which would treat functions with and without a lexical this identically.

@Caitlin I have similar doubts. Also, I don't particularly like the look of the # syntax. It looks too Perl-ish.

# Edwin Reynoso (9 years ago)

@Erik my bad I'm wrong, .. works fine with a method returning a number, its only for a Number literal