Closed bmeck closed 8 years ago
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 }
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?
Also, does this mean to bind static new() {...}
you would use $this::C.new(1)
?
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.
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 https://github.com/zenparsing/es-function-bind/issues/29#issuecomment-164497826 .
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)
For me Lhs::new
looks ok, since the new
itself is not a valid expression when used alone.
@bergus, exactly!
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…)
.
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?
However, I agree that this is completely different proposal. Can we reuse otherwise invalid form of the current proposal - that is the question.
@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.
@zenparsing but new
, typeof
, void
etc. are not functions, it should be a different proposal.
@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).
@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.
also if ::new
lands I would be much more comfortable if all unary operators landed as it would not be a special snowflake.
@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?
@zenparsing something
is:
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.
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.
@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.
@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?
@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.
@zenparsing meta-properties are different for a few reasons:
::new
produces a wrapper that other ::
uses do not]).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!
@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
@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
.
It also is confusing since
typeof
andvoid
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.
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 ; )
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.
@bmeck It technically is, but in a sense, it's binding C.[[Construct]]
, the internal constructor method of C
in this particular case.
@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.
@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.
@isiahmeadows my argument is that:
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.
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.
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,
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 https://github.com/zenparsing/es-function-bind/issues/29#issuecomment-167106475 .
Closing this out, since we'd moved it to the "future extensions" section.
Isn't
ambiguous with regard to
Foo::new
, shouldn't it be something more likenew::Foo
?