Guards are now ready for discussion (was: Nov 18 notes)
On Nov 21, 2010, at 7:40 PM, Mark S. Miller wrote:
On Sun, Nov 21, 2010 at 7:05 PM, Brendan Eich <brendan at mozilla.com> wrote: On Nov 18, 2010, at 4:08 PM, Waldemar Horwat wrote: [...] Worse, the use of colon in this new for syntax is confusingly similar to long-standing future-proofing intentions around runtime type annotations (aka guards or contracts).
(BTW, I don't think :: is a good type annotation or guard punctuator, btw -- it's the C++ namespace qualification operator, also specified for namespacing by ECMA-357 (E4X) and used that way in ActionScript 3 (and in ES4, RIP). So I was surprised to see :: used for annotation-like syntax in strawman:guards and strawman:trademarks.)
Note that I do not consider the trademarks strawman ready for discussion. Accordingly I had recently moved it from the strawman page to the deferred page. I want to do a rather extensive rewrite before I'll be happy enough with it.
Thanks, I didn't mean to spotlight it, hence my parentheses (and double BTW/btw ;-).
I think the guards strawman is in fine shape for discussion. It postpones most of the semantic issues to other specs (like trademarks eventually) that would parameterize the guards strawman by proposing a concrete meaning for its internal Coerce___ function.
The reason I chose "::" over ":" is not for a love of "::". Alternative suggestions appreciated! However, I avoided ":" so that we could guard properties within object literals strawman:guards#guarding_properties. I don't see a way to use ":" for that without confusion. The ES4 solution -- that one can only annotate an object literal as a whole but not its individual properties -- always seemed unpleasant.
:: is strictly less pleasant, and really kind of "taken" due to the precedents I cited. The "less pleasant" point is the big one IMHO.
On Sun, Nov 21, 2010 at 7:48 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 21, 2010, at 7:40 PM, Mark S. Miller wrote:
On Sun, Nov 21, 2010 at 7:05 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 18, 2010, at 4:08 PM, Waldemar Horwat wrote: [...] Worse, the use of colon in this new for syntax is confusingly similar to long-standing future-proofing intentions around runtime type annotations (aka guards or contracts).
(BTW, I don't think :: is a good type annotation or guard punctuator, btw -- it's the C++ namespace qualification operator, also specified for namespacing by ECMA-357 (E4X) and used that way in ActionScript 3 (and in ES4, RIP). So I was surprised to see :: used for annotation-like syntax in strawman:guards and strawman:trademarks.)
Note that I do not consider the trademarks strawman ready for discussion. Accordingly I had recently moved it from the strawman page to the deferred page. I want to do a rather extensive rewrite before I'll be happy enough with it.
Thanks, I didn't mean to spotlight it, hence my parentheses (and double BTW/btw ;-).
I think the guards strawman is in fine shape for discussion. It postpones most of the semantic issues to other specs (like trademarks eventually) that would parameterize the guards strawman by proposing a concrete meaning for its internal Coerce___ function.
The reason I chose "::" over ":" is not for a love of "::". Alternative suggestions appreciated! However, I avoided ":" so that we could guard properties within object literals < strawman:guards#guarding_properties>. I don't see a way to use ":" for that without confusion. The ES4 solution -- that one can only annotate an object literal as a whole but not its individual properties -- always seemed unpleasant.
:: is strictly less pleasant, and really kind of "taken" due to the precedents I cited. The "less pleasant" point is the big one IMHO.
Can we find something better than "::" that doesn't conflict with the ":" in the object literal syntax? As I said, I have no love for "::".
On Sun, Nov 21, 2010 at 7:48 PM, Brendan Eich <brendan at mozilla.com> wrote:
:: is strictly less pleasant, and really kind of "taken" due to the precedents I cited. The "less pleasant" point is the big one IMHO.
Since this is a purely bikeshedding issue, I don't need to be too embarrassed by posting the first color. How about "@"?
On Sun, Nov 21, 2010 at 7:58 PM, Mark S. Miller <erights at google.com> wrote:
On Sun, Nov 21, 2010 at 7:48 PM, Brendan Eich <brendan at mozilla.com> wrote:
:: is strictly less pleasant, and really kind of "taken" due to the precedents I cited. The "less pleasant" point is the big one IMHO.
Since this is a purely bikeshedding issue, I don't need to be too embarrassed by posting the first color. How about "@"?
If we think of guards as annotations, then this kinda sorta has a precedent in Java annotations (if you squint hard enough).
On Nov 21, 2010, at 7:52 PM, Mark S. Miller wrote:
On Sun, Nov 21, 2010 at 7:48 PM, Brendan Eich <brendan at mozilla.com> wrote:
The reason I chose "::" over ":" is not for a love of "::". Alternative suggestions appreciated! However, I avoided ":" so that we could guard properties within object literals strawman:guards#guarding_properties. I don't see a way to use ":" for that without confusion. The ES4 solution -- that one can only annotate an object literal as a whole but not its individual properties -- always seemed unpleasant.
:: is strictly less pleasant, and really kind of "taken" due to the precedents I cited. The "less pleasant" point is the big one IMHO.
Can we find something better than "::" that doesn't conflict with the ":" in the object literal syntax? As I said, I have no love for "::".
Annotating property names in object initialisers is a relatively infrequent use-case -- a hard case that should not impose double the number of colons on all annotated declarations (formal parameters, let/const/var, function return types).
The ML-ish solution, which is forward compatible, is to parenthesize:
let typedObj = { (prop1:type1): value1, (prop2:type2): value2, ... };
The parentheses hurt too, but only this case. And it still may win to annotate the entire ObjectLiteral sometimes, which would avoid (or move to one common guard-type definition) the property-wise annotation overhead. Comments?
On Mon, Nov 22, 2010 at 6:54 AM, Brendan Eich <brendan at mozilla.com> wrote:
The ML-ish solution, which is forward compatible, is to parenthesize:
let typedObj = { (prop1:type1): value1, (prop2:type2): value2, ... };
The parentheses hurt too, but only this case. And it still may win to annotate the entire ObjectLiteral sometimes, which would avoid (or move to one common guard-type definition) the property-wise annotation overhead. Comments?
I wouldn't want to introduce forced parens for the sake of disambiguation for the coder. I'd rather go with ::, # or @ ...
Actually, for object literal I'd go for an equal sign (=), but that obviously won't fix var declarations where the colon is fine.
Can't the guards proposal be applied to function parameters as well? Or am I stepping on land mines there? :) It seems like the same logic could be applied. In that case the equal sign is maybe even more preferable.
On Nov 22, 2010, at 12:37 AM, Peter van der Zee wrote:
On Mon, Nov 22, 2010 at 6:54 AM, Brendan Eich <brendan at mozilla.com> wrote:
The ML-ish solution, which is forward compatible, is to parenthesize:
let typedObj = { (prop1:type1): value1, (prop2:type2): value2, ... };
The parentheses hurt too, but only this case. And it still may win to annotate the entire ObjectLiteral sometimes, which would avoid (or move to one common guard-type definition) the property-wise annotation overhead. Comments?
I wouldn't want to introduce forced parens for the sake of disambiguation for the coder. I'd rather go with ::, # or @ ...
Those are not all equally good, and you're again sacrificing the common case (especially with ::) where there's no need to parenthesize, for the uncommon case of property-wise type annotation in an object initialiser.
Actually, one can use : just fine in an initialiser provided one or two colons are required per property initialiser. There's no need for :: or anything else except to avoid a confusing re-use of colon. Which is an issue, but :: doesn't exactly address it!
Fundamenally, though, you have to weight each use-case by frequency. If as I argue var x : T, function f(a: T, b: U): V {...}, etc. annotations dominate the rare var typedObj = ... case, then parens or whatever is needed (even a separated type annotation for the whole initialiser, as in ES4) is worth it.
Actually, for object literal I'd go for an equal sign (=), but that obviously won't fix var declarations where the colon is fine.
We're not changing object initialiser syntax.
Can't the guards proposal be applied to function parameters as well? Or am I stepping on land mines there? :) It seems like the same logic could be applied. In that case the equal sign is maybe even more preferable.
What are you proposing = be used for, if not default parameter values (see the wiki) or assignment? No way does = make sense for type annotation.
But yes: type annotation (guard, contract, whateve -- not static types but that's not at issue) should be possible for function parameters, return types, local variables, let bindings, catch vars, etc.
On Mon, Nov 22, 2010 at 9:43 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 22, 2010, at 12:37 AM, Peter van der Zee wrote:
On Mon, Nov 22, 2010 at 6:54 AM, Brendan Eich <brendan at mozilla.com> wrote:
The ML-ish solution, which is forward compatible, is to parenthesize:
let typedObj = { (prop1:type1): value1, (prop2:type2): value2, ... };
The parentheses hurt too, but only this case. And it still may win to annotate the entire ObjectLiteral sometimes, which would avoid (or move to one common guard-type definition) the property-wise annotation overhead. Comments?
I wouldn't want to introduce forced parens for the sake of disambiguation for the coder. I'd rather go with ::, # or @ ...
Those are not all equally good, and you're again sacrificing the common case (especially with ::) where there's no need to parenthesize, for the uncommon case of property-wise type annotation in an object initialiser.
Actually, one can use : just fine in an initialiser provided one or two colons are required per property initialiser. There's no need for :: or anything else except to avoid a confusing re-use of colon. Which is an issue, but :: doesn't exactly address it!
As you noted yourself in the other thread, if iterators take on the syntax
of for (var x : y : z)
, the single colon for guards would introduce
ambiguity. (And I believe "::" could fix that?)
Fundamenally, though, you have to weight each use-case by frequency. If as I argue var x : T, function f(a: T, b: U): V {...}, etc. annotations dominate the rare var typedObj = ... case, then parens or whatever is needed (even a separated type annotation for the whole initialiser, as in ES4) is worth it.
Actually, for object literal I'd go for an equal sign (=), but that obviously won't fix var declarations where the colon is fine.
We're not changing object initialiser syntax.
Can't the guards proposal be applied to function parameters as well? Or am I stepping on land mines there? :) It seems like the same logic could be applied. In that case the equal sign is maybe even more preferable.
What are you proposing = be used for, if not default parameter values (see the wiki) or assignment? No way does = make sense for type annotation.
I'm sorry, I did mean "=" for guard. But reading it back, you are right; it doesn't make sense.
On Nov 22, 2010, at 1:03 AM, Peter van der Zee wrote:
On Mon, Nov 22, 2010 at 9:43 AM, Brendan Eich <brendan at mozilla.com> wrote: On Nov 22, 2010, at 12:37 AM, Peter van der Zee wrote:
On Mon, Nov 22, 2010 at 6:54 AM, Brendan Eich <brendan at mozilla.com> wrote:
The ML-ish solution, which is forward compatible, is to parenthesize:
let typedObj = { (prop1:type1): value1, (prop2:type2): value2, ... };
The parentheses hurt too, but only this case. And it still may win to annotate the entire ObjectLiteral sometimes, which would avoid (or move to one common guard-type definition) the property-wise annotation overhead. Comments?
I wouldn't want to introduce forced parens for the sake of disambiguation for the coder. I'd rather go with ::, # or @ ...
Those are not all equally good, and you're again sacrificing the common case (especially with ::) where there's no need to parenthesize, for the uncommon case of property-wise type annotation in an object initialiser.
Actually, one can use : just fine in an initialiser provided one or two colons are required per property initialiser. There's no need for :: or anything else except to avoid a confusing re-use of colon. Which is an issue, but :: doesn't exactly address it!
As you noted yourself in the other thread, if iterators take on the syntax of
for (var x : y : z)
, the single colon for guards would introduce ambiguity. (And I believe "::" could fix that?)
First, I did not argue that there was a formal grammar ambiguity. There may not be, but it still is ugly and confusing.
Second, you're arguing backwards -- we do not have for (x : z) today, it is not obvious we should add it to the language, so it does not follow that we must use :: for type annotations. The precedent for : as type annotation punctuator is stronger than the precedent for :: in the same role; meanwhile, from E4X and AS3 and other old-ES4 offshoots, we have :: for namespace qualification (C++ has that too). So :: is just wrong all around.
Before reaching in a reactive way for @, let's slow down and consider what got us here: over-eager bikeshedding of : instead of "in" to try to split for-in into two loops, to make programmers prejudge whether they want enumeration or something potentially meta-programmable (but not necessarily -- we did not decide that for (x : z) fails if z is not a proxy with an iterate trap, and that doesn't make much sense).
So as dherman pointed out, we're really just trying to replace for-in with for-: as the new for-in. But if the syntax is ugly and confusion, if not ambiguous, and if the new form won't work in old browsers anyway, what good are we doing? Defending against someone porting or mixing JS code into Harmony context and having a for-in loop that expects string keys fail because it got Fibonacci numbers?
On Mon, Nov 22, 2010 at 1:09 AM, Brendan Eich <brendan at mozilla.com> wrote:
[...] Before reaching in a reactive way for @, let's slow down and consider what got us here: over-eager bikeshedding of : instead of "in" [...]
That's not what got me here. For me the issue is uniformity of guard syntax across all contexts where we need them -- including properties of object literals. I agree it is the uncommon case. But the non-uniformity of using one syntax for this case and another syntax for the more common cases bugs me. Likewise, the technically unambiguous
{ foo : G : 33 }
I find too visually confusing and ugly. As you are fond of saying, notation is user interface.
On Nov 22, 2010, at 5:42 AM, Mark S. Miller wrote:
On Mon, Nov 22, 2010 at 1:09 AM, Brendan Eich <brendan at mozilla.com> wrote: [...] Before reaching in a reactive way for @, let's slow down and consider what got us here: over-eager bikeshedding of : instead of "in" [...]
That's not what got me here. For me the issue is uniformity of guard syntax across all contexts where we need them -- including properties of object literals. I agree it is the uncommon case. But the non-uniformity of using one syntax for this case and another syntax for the more common cases bugs me. Likewise, the technically unambiguous
{ foo : G : 33 }
I find too visually confusing and ugly.
Agreed. My point in reply to Peter was that it may be formally unambiguous, but it's still "ugly and confusing".
As you are fond of saying, notation is user interface.
And how! But doubling a colon is not better interface. Especially when that double-tax hits all declarations, not just object initialisers.
Let's see if I can write down some alternatives and list trade-offs:
-
let typedObj = { foo : 33 } : { foo : G }; // a la ES4
-
let typedObj = { foo :: G : 33 }; // the guards strawman
-
let typedObj = { (foo : G) : 33 }; // the ML-ish way
-
let typedObj = { foo @ G : 33 }; // funny cartoon chars
Trade-offs:
0+ avoids running type^H^H^H^Hguard together with property name and value using :, ::, @, or whatever, instead paralleling the whole initialiser
0+ allows commoning the guard initialiser expression so it can be reused among multiple object value initialisers
0- parallel structure duplicates all property names (and order? not clear), which is tedious at any scale and error-prone for large initialisers
1+ avoids restating names (and order?)
1- imposes double-colon tax on all annotation contexts, even though only this one wants it for visual distinctiveness (not disambiguation of formal grammar)
1- :: is an eyesore (yet arguably not distinct enough from single : -- see last 1- item), and has quite different precedent as namespace qualification operator in Ecma and other standards
2+ keeps : as annotation punctuator
2- at the price of parenthesizing the property name in object initialisers
3+ avoids overloading colon in object initialisers
3- flouts programming language (including JS derivatives) precedent for using : as annotation punctuator
3- may collide with JScript's preprocessor, also may collide with @attributes in E4X, uses up @ in a novel way where we might want to reserve it for another use
Comments:
We don't have too many other funny chars (# is wanted for concise function syntax). This suggests trying to use : and not breaking the common annotation case over this initialiser hard case.
Sometimes a (commoned, reusable) annotation at the end of the whole object initialiser is pure win. It's really not clear at this point that only at the end, or only interspersed guards, are the one way we should standardize.
If guards are uncommon in object initialisers, parenthesization is arguably fine, not only as a tax users can pay but as a visual flag that something is up.
Let's consider the likely far more common use-cases:
var x : T = v;
function f(a: U, b: V): W {...}
... { ... let x : T = v; ... }
while @ or another char could work, the tradition favoring : combined with the scarcity of unused punctuators makes me want to stick with colon. That, plus the relative rarity of annotated initialisers (in my best guess -- we can argue about this or try to estimate by adding guards to existing code), make me still favor (2).
On 2010-11-22, at 13:13, Brendan Eich wrote:
That, plus the relative rarity of annotated initialisers (in my best guess -- we can argue about this or try to estimate by adding guards to existing code), make me still favor (2).
The idea of guards as "adjectives" (ala C) is a non-starter?
Would keyword arguments ever be added to JS? Any chance that would influence the choices made here?
On Nov 22, 2010, at 10:58 AM, P T Withington wrote:
On 2010-11-22, at 13:13, Brendan Eich wrote:
That, plus the relative rarity of annotated initialisers (in my best guess -- we can argue about this or try to estimate by adding guards to existing code), make me still favor (2).
The idea of guards as "adjectives" (ala C) is a non-starter?
That is awkward if you do require a leading var/let/const in addition to the annotation:
var Int32Like i = 0;
and we don't want to reserve the names of all possible guards.
Also, guards or runtime types are not static types, so it seems better to avoid reusing type declarator order from static languages in the C family.
Functions are easier to extend this way:
function foo(MyGuardA a, MyGuardB b) GuardForReturnValue { ... }
but the point about not copying type-then-declarator stands.
Would keyword arguments ever be added to JS? Any chance that would influence the choices made here?
Imponderable unless we reserve : in call expressions such as
foo(arg1: val1, arg3: val3);
to mean named formal parameter association -- keyword arguments if I understand what you meant by that. Yet this is both too close to an object initialiser, and also not ambiguous with annotations in declarations. This is an expression, not a declaration.
On Nov 22, 2010, at 11:12 AM, Brendan Eich wrote:
Would keyword arguments ever be added to JS? Any chance that would influence the choices made here?
Imponderable unless we reserve : in call expressions such as
foo(arg1: val1, arg3: val3);
to mean named formal parameter association -- keyword arguments if I understand what you meant by that. Yet this is both too close to an object initialiser
by which I mean: what's the point? Just pass an object initialiser to a function that destructures it. Can be optimized to avoid constructing an object, in advanced implementations. But whatever the optimization, the language is crowded if we have both of
foo(arg1: val1, arg3: val3);
and today's works-in-all-browsers form:
foo({arg1: val1, arg3: val3});
Is losing the braces really worth the added complexity?
On 2010-11-22, at 14:33, Brendan Eich wrote:
Is losing the braces really worth the added complexity?
Perhaps not. Braces are surely as good a way to denote keyword args as any other flag. It occurred to me that there is no equivalent of object literals in the languages I know that have keyword arguments. There are strong parallels, which is probably why the discussion of the guarded literal syntax brought it to my mind. But that makes me wonder how I would write a function with "keyword arguments" that were both guarded and had default values?
function f (a:V, b:W=w {c:X=x, d:Y=y}) ...
or, the less parallel:
function f (a:V, b:W=w {(c:X):x, (d:Y):y}) ...
On Sun, Nov 21, 2010 at 7:05 PM, Brendan Eich <brendan at mozilla.com> wrote:
Note that I do not consider the trademarks strawman ready for discussion. Accordingly I had recently moved it from the strawman page to the deferred page. I want to do a rather extensive rewrite before I'll be happy enough with it.
I think the guards strawman is in fine shape for discussion. It postpones most of the semantic issues to other specs (like trademarks eventually) that would parameterize the guards strawman by proposing a concrete meaning for its internal Coerce___ function.
The reason I chose "::" over ":" is not for a love of "::". Alternative suggestions appreciated! However, I avoided ":" so that we could guard properties within object literals < strawman:guards#guarding_properties>.
I don't see a way to use ":" for that without confusion. The ES4 solution -- that one can only annotate an object literal as a whole but not its individual properties -- always seemed unpleasant.