-
Notifications
You must be signed in to change notification settings - Fork 25
How is the receiver treated? #1
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
Comments
var _temp;
(_temp = x.y, (a, ...b) => _temp.z(a, ...b)); |
I'll add this example to the strawman. |
I like these semantics! |
Hmm. This does beg the question as to whether we should eagerly evaluate all fixed arguments as well, or lazily evaluate. The strawman indicates lazy evaluation which preserves side effects. As such, my example above is inconsistent. The more consistent approach would be to have |
That's a legitimate argument, though my intuition is more that the receiver is different, e.g., it can't be substituted with Nit-pick: I'd prefer avoiding saying "lazy evaluation" here--that often means evaluating something once and caching it. Here, it's evaluating it separately each time |
True. I don't intend to use the term 'lazy' in the strawman. |
Generally |
Well, the question then becomes, how far back do you go to capture the calculation of the receiver? What is I don't think it's reasonable to make the mental model for all new features be a trivial syntactic translation to old features. Sometimes, it can make things worse (here, due to the scope ambiguity issue). |
I think the syntactic conversion is besides the point. However, this is a good question to pose to the committee: Do we preserve existing runtime semantics to be consistent with the rest of the language, or is it better to follow what might be the common intuition that the receiver should be fixed once and not reevaluated? I'm erring on the side of the former for now. |
Regarding the question of whether other arguments should be eagerly evaluated, it seems like either direction is going to be surprising e.g.: // With eager this would probably do what you expect
// Create a factory for { fizz() {} } objects
const factory = Object.create({ fizz() {} }, ?)
// With eager this probably wouldn't do what you expect
// namely sharing the same reference causing the same object
// to be many places on the tree
const addLeft = Reflect.set(?/*tree*/, 'left', { left: null, right: null }) I prefer the "lazy" form myself as sharing potentially mutable references generally doesn't seem a great idea to me. I also don't think it's even possible to simulate the "lazy" form using the eager form, whereas the converse is always possible just by supplying a reference: // With "lazy" you can always do this:
const proto = { fizz() {} }
const factory = Object.create(proto, ?)
// No such analog for eager as you can't make an expression become lazy
// after the fact You could even in theory have some syntax for coercing the arguments to be evaluated at definition time (or you could make it eager by default but use the operator to defer to call time) e.g.: // Using % just for exposition
const factory = Object.create(%{ fizz() {} }, ?)
function div(a, b) {
if (b === 0) {
throw new Error("Division by zero!")
}
return a/b
}
// Using such an operator would throw at creation time
const add5 = add(%div(1, 0), ?) // Throws immediately not at call time
// ------
// Or conversely if it was eager by default:
// the syntax would be used to say I want this lazily evaluated each call
const addLeft = Reflect.set(?, 'left', %{ left: null, right: null }) |
I just thought of another use case that may be worth adding to the README: class Control extends HTMLElement {
constructor() {
super();
this.onclick = this.clicked(?);
}
clicked(e) {
}
} |
I agree that consistency is a good goal in decisions like this. I don't see how one or the other of the options here is more consistent with the rest of the language. Could you explain more? Evaluating an expression multiple times that might look like it's evaluated just once, syntactically, could be considered less consistent with the rest of the language; that's my intuition here, personally. |
I think it could be useful first to decide using of which thing we want to make more comfortable - either arrow functions or .bind function. |
If someone looks at a function, either regular or arrow or generator or async, he understands that this piece of code have to be executed at some other moment, not right now. So functions declare code, but do not evaluate it. |
In something like
A couple questions:
x.y.z
and call it with the argument, or does it call withx.y
as the receiver?x.y
evaluated each time, or just once at the beginning? How about the GetMethod ofz
?a[x].b().c.d().y.z(?)
--where does the chain cut off exactly? Should this somehow correspond with optional chaining's extent?I'm asking because @rbuckton and I have talked about alternatives here, and I don't see the answer explicitly mentioned in the document (though I guess the
.call()
examples would not work if the receiver is not preserved somewhat).The text was updated successfully, but these errors were encountered: