Proposal: The Conditional Fail and Recover Operators; or: a more generic Existential Operator
Le 22 mai 2014 à 23:58, Claude Pache <claude.pache at gmail.com> a écrit :
Hi,
Today, I came to a case where I wanted to grab a reference to some getter of the DOM, but to not fail if it doesn't exists:
try { originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement.prototype, 'elements').get } catch(e) { originalElementsGetter = undefined }
(If you are curious: the context is monkey-patching
<form>.elements
, so that it includes the<output>
elements of the form, for I've just discovered that IE forgets to include them.)This seems to be a typical case for the Existential Operator (?.) we spoke recently about (although, in my precise case, a try/catch does the trick). However, the Existential Operator works only on properties and methods:
// Bad: if window.HTMLFormElement?.prototype is undefined, Object.getOwnPropertyDescriptor will protest loudly. originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement?.prototype, 'elements')?.get // Here is what I could use: Object.defineProperty(Object.prototype, '$getOwnPropertyDescriptor', { value: function(prop) { return Object.getOwnPropertyDescriptor(this, prop) } , writable: true , configurable: true }) oldElementsGetter = window.HTMLFormElement?.prototype?.$getOwnPropertyDescriptor('elements')?.get
Can we do better?
I want to propose a mechanism that can simply express the following (only partially fulfilled by the Existential Operator):
- check for nully (null or undefined) values at precise spots of an expression being evaluated;
- if a nully is found, stop the evaluation of the entire current (sub)expression, and yield undefined;
- or, instead of yielding undefined, provide an alternate value;
- and be able to determine precisely what "the current subexpression" of point 2 means.
So, let's introduce the Conditional Fail Operator (
??
), and, its counterpart, the Recover Operator (!!
). (You could think of them as a throw/catch-like mechanism, or, better, as a break/block-like mechanism.) For the sake of illustration, I have artificially lengthen my original example:
originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement??.prototype??, 'elements')??.get?? !! Object.getOwnPropertyDescriptor(window.HTMLElement??.prototype??, 'elements')??.get?? !! Object.getOwnPropertyDescriptor(window.Element??.prototype??, 'elements')??.get
The semantics are the following:
??
is a unary operator that is used as suffix. If its operand is nully (null or undefined), the evaluation of a certain expression (more precisely defined below) that it is part of, fails immediately. Otherwise, the operand is just returned as is.!!
is a short-circuiting binary operator. It evaluates its LHS. If the evaluation is not interrupted by a??
, it returns the result. But if the evaluation is interrupted by a??
, it evaluates its RHS and returns the result.- For any assignement operator (
=
,+=
, etc.), if the evaluation of its RHS is interrupted by a??
, the RHS is replaced byundefined
and the assignment is performed normally.- All other operators and function calls are transparent to the failure signal send by
??
(you have to stop it by an explicit!!
).
I see a nasty footgun in the last two bullets, in case someone refactors from foo.bar = baz??
to fooMap.set('bar', baz??)
, for the latter will fail silently.
But we do want function calls to be transparent to ??
-failure signals, as it can seen in my original example.
The best alternative I can see, is the following:
- All operators (excluding
!!
, of course, but including assignments) and function calls are transparent to failure signals. - If a failure signal remains uncaught, an error is thrown (and this condition may even be statically determined and produce an early error). This will catch (!) early failed-assignment bugs, and force the programmer to be more explicit.
With this amendment, my example must be completed with a final !! undefined
:
originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement??.prototype??, 'elements')??.get??
!! Object.getOwnPropertyDescriptor(window.HTMLElement??.prototype??, 'elements')??.get??
!! Object.getOwnPropertyDescriptor(window.Element??.prototype??, 'elements')??.get??
!! undefined
Since nobody gave their advice, I'll give my own one :-)
The particular case of the Existential Operator (conditional property access and conditional method call) is probably the more usual one. The case that I 've come across, which is maybe the more common one not covered by the Existential Operator, consists of using a static method, but thinking of it as if it were a method of its first argument. Some syntactic sugar could help to both replace it to the correct position (at the right side of the object) and integrate it well with the Existential Operator. Thus, I think we could explore rather that direction.
Ok, I stop my monologue here...
—Claude
Le 23 mai 2014 à 09:18, Claude Pache <claude.pache at gmail.com> a écrit :
I like the basic idea. I think the problem is that this is too original for a language like ECMAScript. There isn't any prior body of experience of what the use cases and consequences would be.
I'd suggest submitting the proposal to the Traceur people:
code.google.com/p/traceur-compiler
I might play with this in my own in-house ES6 compiler, but unfortunately I'm the only using it (heh, or who would want too; it's terrible code; purely a production tool).
This gives me another idea. We need a community wiki for new, experimental language ideas. A kind-of pre-strawman wiki, I guess. Someplace where people could post proposals, experimental compiler projects like Tracuer (and even related languages like CoffeeScript), could implement them and post feedback, etc. Proposals could then be forwarded to the ecmascript committee after accumulating enough experience of how they work in practice.
Best, Joe
At JSConf there was a talk about using macros to experiment with new language features. The library mentioned is sweet.js (sweetjs.org).
Maybe you could implement it as a macro for developers to play around?
Today, I came to a case where I wanted to grab a reference to some getter of the DOM, but to not fail if it doesn't exists:
try { originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement.prototype, 'elements').get } catch(e) { originalElementsGetter = undefined }
(If you are curious: the context is monkey-patching
<form>.elements
, so that it includesthe
<output>
elements of the form, for I've just discovered that IE forgets to include them.)This seems to be a typical case for the Existential Operator (?.) we spoke recently about (although, in my precise case, a try/catch does the trick). However, the Existential Operator works only on properties and methods:
// Bad: if window.HTMLFormElement?.prototype is undefined, Object.getOwnPropertyDescriptor will protest loudly. originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement?.prototype, 'elements')?.get // Here is what I could use: Object.defineProperty(Object.prototype, '$getOwnPropertyDescriptor', { value: function(prop) { return Object.getOwnPropertyDescriptor(this, prop) } , writable: true , configurable: true }) oldElementsGetter = window.HTMLFormElement?.prototype?.$getOwnPropertyDescriptor('elements')?.get
Can we do better?
I want to propose a mechanism that can simply express the following (only partially fulfilled by the Existential Operator):
So, let's introduce the Conditional Fail Operator (
??
), and, its counterpart, the Recover Operator (!!
). (You could think of them as a throw/catch-like mechanism, or, better, as a break/block-like mechanism.) For the sake of illustration, I have artificially lengthen my original example:originalElementsGetter = Object.getOwnPropertyDescriptor(window.HTMLFormElement??.prototype??, 'elements')??.get?? !! Object.getOwnPropertyDescriptor(window.HTMLElement??.prototype??, 'elements')??.get?? !! Object.getOwnPropertyDescriptor(window.Element??.prototype??, 'elements')??.get
The semantics are the following:
??
is a unary operator that is used as suffix. If its operand is nully (null or undefined), the evaluation of a certain expression (more precisely defined below) that it is part of, fails immediately. Otherwise, the operand is just returned as is.!!
is a short-circuiting binary operator. It evaluates its LHS. If the evaluation is not interrupted by a??
, it returns the result. But if the evaluation is interrupted by a??
, it evaluates its RHS and returns the result.=
,+=
, etc.), if the evaluation of its RHS is interrupted by a??
, the RHS is replaced byundefined
and the assignment is performed normally.??
(you have to stop it by an explicit!!
).As I have shown in [1], it seems relatively easy to spec such a behaviour using a custom Abrupt Completion. The proposed spec needs to be refined in order to avoid precocious GetValue(...) calls, but I am able to write a complete strawman in case there is interest.
WDYT?
—Claude
[1] esdiscuss.org/topic/specifying