-
-
Notifications
You must be signed in to change notification settings - Fork 16
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
Special-casing particular expression types is a footgun #12
Comments
This particular example is still confusing me: function example() {
return {
[Symbol.result]() {
return [new Error("123"), null]
},
}
}
const [error, result] ?= example() // Function.prototype also implements Symbol.result
// const [error, result] = example[Symbol.result]()
// error is Error('123') This implies it is evaluated as: How will the function instance passed on to the handler on the prototype? Is it an implied mechanism similar to class A {
internalValue = 3;
myMethod() {
return this.internalValue;
}
}
const a = new A();
a.myMethod(); // works as expected - "this" is set to "a"
const b = a.myMethod;
b(); // doesn't have a reference to the instance What is the equivalent mechanism for the result symbol? It should be possible to implement a custom version of this mechanism, be it for poly-filling reasons or other. Edit: the class example is maybe somewhat irrelevant, there is no way we loose track of the function for the new operator, but I am asking how it is passed in to the result handler? Maybe |
Ok, I think it is obviously based on const result = Symbol("result")
function example() {
return {
[result]() {
return [new Error("123"), null]
}
}
}
Function.prototype[result] = function() {
return [this()[result](), null];
}
const [value, error] = example[result]();
console.log(value, error); For the object version: const result = Symbol("result")
const example = {
[result]() {
return [new Error("123"), null]
}
};
Object.prototype[result] = function() {
return [this[result](), null];
}
const [value, error] = example[result]();
console.log(value, error); Notice the difference between the two implementations - we are calling it if it is a function and not if it is an object. That special casing is the reason why I agree with @pie-flavor. |
This spec describes that
[err, res] ?= func(x)
is evaluated likefunc.@@result(x)
. However, it also describes that[err, res] ?= obj
is evaluated likeobj.@@result()
. This would imply that[err, res] ?= (func(x))
is evaluated likefunc(x).@@result()
. One can easily imagine a code formatting style, preprocessor, or bulk-editing oversight that creates a case like that.Specifying that non-existing
@@result
producesTypeError
would protect against this case, except that becausePromises
are handled via a general rule of recursion, no such error would be thrown for a Promise-returning function (or other@@result
-implementing value).This can produce a line of code that the author expects will never throw, but can throw.
More generally, this syntax breaks other expectations about general programming, such as that evaling an expr is no different from assigning it to a variable and then evaling the variable. An example of a function call being treated specially would be Go's
defer f(x)
evaluatingx
immediately instead of at the time of invocation, but Go counterbalances this by forbidding any other kind of expression so if you screw it up you get an immediate error.In my opinion, if a function call expr is treated specially, all other types of expr should be forbidden. An element of syntax can be an alternative way to call a function, or an operator on the result of an expression, but should never be both. (This implies processing
await foo(x)
directly as a second case, instead of through recursion.)The text was updated successfully, but these errors were encountered: