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

::new collision? #29

Closed
bmeck opened this issue Dec 14, 2015 · 36 comments
Closed

::new collision? #29

bmeck opened this issue Dec 14, 2015 · 36 comments

Comments

@bmeck
Copy link
Member

bmeck commented Dec 14, 2015

Isn't

class Foo {
  static new() {
    console.log(123);
  }
}
Foo.new();
new Foo();

ambiguous with regard to Foo::new, shouldn't it be something more like new::Foo?

@zenparsing
Copy link
Member

Thanks for bringing this up; I was hoping we could get some discussion going!

Remember that the binary form never looks up properties on the RHS:

obj::blahblah;  // Bind 'obj' to function 'blahblah'

In the above, we don't look up the "blahblah" property of "obj".

The best way to think of obj::new is to regard it as sugar for this:

obj::function(...args) { return new this(...args) }

(Currently, it's not implemented quite that way, but it's close enough.)

So the RHS can define a "new" property and there's no conflict.

class C {
    constructor(a) {
        this.a = a;
    }
    static new {
        return "Hello";
    }
}

console.log(C.new(1)); // "Hello"
console.log(C::new(1)); // { a: 1 }

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

idk this is very confusing to me since new is not a function, would this mean typeof, and void would also be valid to bind?

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

Also, does this mean to bind static new() {...} you would use $this::C.new(1) ?

@domenic
Copy link
Member

domenic commented Dec 14, 2015

Yes, I think this C::new syntax is not going to make it personally, and am choosing not to worry about it. It's just too confusing; it's tacking a new proposal onto this one and reusing the same syntax.

@WebReflection
Copy link

Agreed with Domenic, I personally wouldn't create a precedent where a
generic property like new should be reserved for the :: syntax.

We passed that phase on ES5 already, allowing obj.class, obj.new and
all others, it'd be a bad and (IMO) unnecessary precedent/exception for
:: where exceptions rarely indicate a good design choice.

Just my 2 cents

On Mon, Dec 14, 2015 at 5:11 PM, Domenic Denicola notifications@github.com
wrote:

Yes, I think this C::new syntax is not going to make it personally, and
am choosing not to worry about it. It's just too confusing; it's tacking a
new proposal onto this one and reusing the same syntax.


Reply to this email directly or view it on GitHub
#29 (comment)
.

@bergus
Copy link

bergus commented Dec 14, 2015

Uh, I was shortly confused by this now, but actually it sorts out quite simple.

Methods that are named new can still be bound using the unary operator:

::Foo.new

while only the binary form is attaching a special behavior to the new keyword that would otherwise not even be allowed in this position - it's not a valid identier (variable name).

Foo::x   // x.bind(Foo)
Foo::new // new.bind(Foo) - invalid anyway, therefore we desugar that to
         // (...args) => new Foo(...args)

@Artazor
Copy link

Artazor commented Dec 14, 2015

For me Lhs::new looks ok, since the new itself is not a valid expression when used alone.

@bergus, exactly!

@bergus
Copy link

bergus commented Dec 14, 2015

Maybe we should flip the operands and use

new::Foo

instead. Imo this is better because the function that is evenutally going to be called is always on the right side. And there's less confusion, nobody can mistake this as a property access on new.

The only disadvantage could be that it makes the grammar a bit more complicated, as we must prevent this from parsing as new (::Foo…).

@WebReflection
Copy link

you are both expecting a special meaning for a name ... try for a second to think new is the exact same thing as whatever and rethink your examples? That's all I was trying to say.

:: is not for classes only, it's a generic syntax sugar, right?

@Artazor
Copy link

Artazor commented Dec 14, 2015

However, I agree that this is completely different proposal. Can we reuse otherwise invalid form of the current proposal - that is the question.

@zenparsing
Copy link
Member

@bergus Right, new::Foo would require a lookahead restriction within the MemberExpression production.

While technically possible, I don't think it makes sense. You're not binding something to the constructor. You're binding the constructor to a "generic newer method", i.e.

function genericNewer(...args) {
    return new this(...args);
}

const fooNewer = genericNewer.bind(Foo);

Instead of that, we can simple write:

const fooNewer = Foo::new;

@WebReflection Yes, :: is sugar for binding an object to a function.

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

@zenparsing but new, typeof, void etc. are not functions, it should be a different proposal.

@zenparsing
Copy link
Member

@bmeck Keywords are re-used in various places, e.g. new.target. The fact that new is not actually an object doesn't mean that we can't reuse the keyword in very targeted ways.

The benefit of Foo::new is that it removes pressure on classes to make them somehow by default callable factories (which is a common request).

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

@zenparsing meta-properties are very explicit (the fact that new cannot be an identifier), while I don't feel that this syntax is clear since it is on the right hand side of an operator with similar feeling to the . operator.

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

also if ::new lands I would be much more comfortable if all unary operators landed as it would not be a special snowflake.

@zenparsing
Copy link
Member

@bmeck I can tell that you don't like it, but why exactly? Is it because that you feel it confuses property lookup with binding? If so, then why do you think that's not a problem for the entire proposal? In other words, why is:

Foo::new;

A problem for you, but,

import { something } from "somewhere";
foo::something();

not?

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

@zenparsing something is:

  1. a valid identifier
  2. a function

new is neither. Treating it as the same syntax is confusing when I see 2 major differences.

It also is confusing since typeof and void which are in the same category of named unary operators are not talked about.

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

Having a syntax with an operator that mimics a binary operator : lhs operator rhs just adds to the above since it makes new a right hand side value only for this operator.

@zenparsing
Copy link
Member

@bmeck But the point is that "something" could equally be confused with property lookup as new. I'm not understanding how Foo::new creates any additional hazard in that regard.

Having a syntax with an operator that mimics a binary operator : lhs operator rhs just adds to the above since it makes new a right hand side value only for this operator.

Again, the same exact argument could be applied to new.target, with the keyword on the LHS.

True, this is a special-case overload of the new operator, and no-one particularly likes special-cases. Special-cases have to be sufficiently justified, and you could argue that the justification isn't adequate.

@Artazor
Copy link

Artazor commented Dec 14, 2015

@WebReflection, you are right.... it is true.

Now I'm started to think that this proposal can be abandoned in favor of plain ES6 helper function

function NEW(...args) { 
    return new this(...args)
}

Or the hypothetical TypeScript (with variadic generics or variadic kinds, and thisArg typing)

function NEW<T, ...Args>{new:(...args:...Args):T}::(...args: ...Args): T {
    return new this(...args);
}

Thus, A::new is effectively modelled with A::NEW

Um?

@zenparsing
Copy link
Member

@Artazor Yeah, possibly it would be better to leave it to user space. If it's common enough, then syntax sugar makes sense (so that user's don't have to constantly import that NEW function).

Either way, I think I'd like to keep in the new lookahead restriction just in case.

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

@zenparsing meta-properties are different for a few reasons:

  1. They are on the LHS of an operator, as such they can form their own basis of authority. RHS affecting how an operator functions is not present in JS, however LHS has precedent with property descriptors.
  2. They expose similar rules as their non-meta counterpart (no special effects occur by using specific aspects of the properties [::new produces a wrapper that other :: uses do not]).

@zenparsing
Copy link
Member

RHS affecting how an operator functions is not present in JS

But new on the right doesn't really affect the semantics of the operator. The new keyword is just shorthand for the "generic newer method" in that context.

::new produces a wrapper that other :: uses do not

Bound functions are all wrappers. Foo::new produces a wrapper just like the other forms.

So I'm just not understanding the arguments.

I think we'll have to agree to disagree for now. Let's let this settle for a few days and see how it looks. Thanks for all the feedback though!

@bmeck
Copy link
Member Author

bmeck commented Dec 14, 2015

@zenparsing agree, not saying it can't go in, but staunchly against it going in the same proposal. Will get back to this on Thursday

@bergus
Copy link

bergus commented Dec 15, 2015

@zenparsing

While technically possible, I don't think it makes sense. You're not binding something to the
constructor. You're binding the constructor to a "generic newer method", i.e.

function genericNewer(...args) {
    return new this(...args);
}
const fooNewer = genericNewer.bind(Foo);

I think you got your statements backwards - you are binding that generic newer method to the constructor, like Foo::genericeNewer would do - but I get what you are saying. Foo::new makes sense from that perspective.

My thoughts were coming from the following scheme:

  x::foo() // == foo.bind(x)(…)
            // == foo.[[call]](x, …)
new::foo() // == new foo(…)
            // == foo.[[construct]](…)

but I've come to the conclusion that neither is really logical.

Maybe making your generic newer function available as Function.prototype.new or Reflect.new would make more sense. The first would even allow using ::Foo.new.
Or just solve the underlying problem by adding a new method

Function.prototype.fromJSON = function(obj) {
    return Object.assign(new this, obj);
};

that should be overwritten by classes (via static fromJSON(obj) { … }) if necessary and that could be passed around via ::Foo.fromJSON.

@bergus
Copy link

bergus commented Dec 15, 2015

It also is confusing since typeof and void which are in the same category of named unary operators are not talked about.

I don't think new is in the same category as these. Neither of them calls a function, and also they don't take arguments lists. In that regard, new is more like a binary function, that is going to get partially applied here. typeof, void, + and other operators that are truly unary would need function composition, not binding.

@zenparsing
Copy link
Member

I think you got your statements backwards

I did, thanks : )

Maybe making your generic newer function available as Function.prototype.new or Reflect.new would make more sense. The first would even allow using ::Foo.new.

Nah, neither of those is really much of an improvement over the current situation as far as passing constructor factories around, since you would have to auto-bind Function.prototype.new or partially apply Reflect.new.

Foo::new, on the other hand, is just right ; )

@bmeck
Copy link
Member Author

bmeck commented Dec 15, 2015

I don't think new is in the same category as these. Neither of them calls a function, and also they don't take arguments lists. In that regard, new is more like a binary function, that is going to get partially applied here. typeof, void, + and other operators that are truly unary would need function composition, not binding.

  • new is unary, it accepts a single operand, a function signature. unsure what you mean that it is binary? do you mean the fact that it evaluates the formals list?
  • as for function calls + could call .valueOf or even .toString.

Foo::new, on the other hand, is just right ; )

Disagree, ::new is a one off (wart). The more I think the more it looks like a square forced into a round hole.

::new even in a way would closer to global.new than one off for the new operator since it does identifier lookup in all other cases. Meta properties cannot have a valid identifier, and identifiers historically are always LHS arguments (within their expression) in JS.

Too many chances for confusion, please keep your proposal clean. I have listed more than 5 ways it can be confusing. Alternatives already exist for handling new, presenting a new way to do things just means more complexity to the language.

Falling back upon meta-properties as a reason that this makes sense seems a bit counter-intuitive. Meta properties are exposing VM state in a way consistent with existing state inspection. Function binding is partially applying (only the this value) of functions, which is already doable today, but lacks sugar. This is new syntax and would be the first time we have a RHS meta identifier.

I want a nicer function binding syntax, not one that introduces yet another exception to remember.

Just wait for MDN: The :: operator does ... , except in the case if new is on the RHS. Exceptions are bad; people don't like them in code, why do they want them in spec?

I have a poll in twitter (that I am not participating in), but am trying to gather votes one way or another on if the exception seems reasonable to other programmers.

@dead-claudia
Copy link
Contributor

@bmeck It technically is, but in a sense, it's binding C.[[Construct]], the internal constructor method of C in this particular case.

@bmeck
Copy link
Member Author

bmeck commented Dec 15, 2015

@isiahmeadows if this was true, the left hand only var f = ::new seems like it would work/use a global [[Construct]]. I simply don't view it that way.

@dead-claudia
Copy link
Contributor

@bmeck I was referencing the [[Construct]] internal method used throughout the spec. And to be honest, it's always awkward using constructors in functional style, because of the new.

And BTW, Java has the same exact syntax for contructor references:

class Averager implements IntConsumer {
    private int total = 0;
    private int count = 0;

    public double average() {
        return count > 0 ? ((double) total) / count : 0;
    }

    public void accept(int i) {
        total += i;
        count++;
    }

    public void combine(Averager other) {
        total += other.total;
        count += other.count;
    }
}

Averager averageCollect = roster.stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(Person::getAge)
    .collect(Averager::new, Averager::accept, Averager::combine);

System.out.println("Average age of male members: " +
    averageCollect.average());

That was kinda my inspiration. But in all honesty, even though I came up with the syntax, I noted in the PR that I wasn't really holding on tight to it. I was more concerned about functionality than ergonomics.

@bmeck
Copy link
Member Author

bmeck commented Dec 15, 2015

@isiahmeadows my argument is that:

  • on one side (RHS) of a binary operator
  • in 1 of 2 uses of that operator
  • using a different operand type than all other uses of that operator
  • using an invalid identifier (slight concession to meta-properties, but could be seen as a hidden identifier in LexicalEnvironment).

Resulting in a different behavior is confusing. Thats a lot of edge casing, just to avoid making a generic way to call constructor functions.

Sure it looks nice, but I think it is out of scope for the initial proposal. If you want to specify a hidden new identifier that has [[Call]] I would be more open to it. This just smells without more clear explanation of what this is doing.

Specifying new as a function would make things much clearer and remove my concerns, but that poses its own problems. Until that point, those 4 points make this sound like people want pretty, not consistent.

@zenparsing
Copy link
Member

I've moved the bound constructor stuff into a "Future Extensions" section. I've left the lookahead restriction in the grammar to allow support if we ever want it.

@meandmycode
Copy link

I think since there is seemingly a lot of debate about ::new then it's sensible to avoid it from the bind specification.

A somewhat related question to this debate, I have been making a lot of use of this new operator (back since abstract references) and plan to release libraries next year that imply using the bind operator, whilst things like babel are making it easy to use this now, I was looking around at what other 'near js' ecosystems are planning, such as typescript, one thing I noticed was that under the stage0 proposals for ecma262, the bind operator isn't flagged as being ready: https://github.com/tc39/ecma262/blob/master/stage0.md

Looking here there don't seem to be any blocking issues for the core binding operator syntax and was wondering if there are plans for how the operator will progress to the next stage, and if there is anything myself or anyone else can do to help here?

Thanks in advance,

@dead-claudia
Copy link
Contributor

The main reason for ::new was a conceptual "binding the constructor" to
make it more FP-friendly. For what it's worth, when I created the patch, it
was only to get the concept and semantics formalized, independent of syntax
(i.e. we're not even sure it's necessary, so it's kind of in a "phase 2
proposal" of sorts).

On Thu, Dec 24, 2015, 07:56 meandmycode notifications@github.com wrote:

I think since there is seemingly a lot of debate about ::new then it's
sensible to avoid it from the bind specification.

A somewhat related question to this debate, I have been making a lot of
use of this new operator (back since abstract references) and plan to
release libraries next year that imply using the bind operator, whilst
things like babel are making it easy to use this now, I was looking around
at what other 'near js' ecosystems are planning, such as typescript, one
thing I noticed was that under the stage0 proposals for ecma262, the bind
operator isn't flagged as being ready:
https://github.com/tc39/ecma262/blob/master/stage0.md

Looking here there don't seem to be any blocking issues for the core
binding operator syntax and was wondering if there are plans for how the
operator will progress to the next stage, and if there is anything myself
or anyone else can do to help here?

Thanks in advance,


Reply to this email directly or view it on GitHub
#29 (comment)
.

@zenparsing
Copy link
Member

Closing this out, since we'd moved it to the "future extensions" section.

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

8 participants