On Nov 9, 2007, at 11:24 PM, Yuh-Ruey Chen wrote: >> function MyConstructor(a, b) { >> return {a: a, b: b} : {a: int, b: string}; >> } >> > > Ah, so expressions can be annotated with types?
Array and object initialisers can be annotated with array and object ("record") structural types to make the expressed object have fixed properties named by the type, instead of just being a plain old Array or Object instance that has ad-hoc ("expando") properties given by the initialiser, where the properties could be deleted in the very next statement. > Didn't see that on the wiki http://wiki.ecmascript.org/doku.php? id=proposals:structural_types_and_typing_of_initializers > and it's not implemented in the RI yet. It looks like it almost works, but for an easy-to-fix bug: >> let q = {p:42} : {p:int} [stack] [init q()] **ERROR** EvalError: typecheck failed, val=obj type={p: [ns public '__ES4__']::int } wanted={p: [ns public '__ES4__']::int } (near <no filename>:1:1-1.3) The val and wanted parts of the diagnostic look the same to me. Should be fixed soon. >> Above you have made three different things. The instanceof check is >> not the same as the |is| check. The type paramter example is yet >> again different -- it's just printing x assuming x is compatible with >> t -- that is, that there's no type error on attempt to call foo.<T>, >> e.g. foo.<Date>(new RegExp). >> > > I acknowledged that |is| is different from |instanceof| but I was > using > |instanceof| because it is similar to |is| yet works on value exprs. A > more proper example would be a combination of |instanceof| and all the > other checks that |is| allows via reflection. Right. You can see from: http://wiki.ecmascript.org/doku.php? id=proposals:syntax_for_type_expressions and Graydon's reply that we originally had 'is' take a value expression right operand, and you had to use 'type E' (the unary type operator mentioned at the bottom of the above-linked page) to force E to be treated as a type expression. We've moved away from that to get experience with the alternative design, where 'is' takes a type expression on the right, in order to get earlier and more certain type checking. But this is all subject to change based on feedback -- such as yours ;-). > What's the rationale behind not throwing a type error when calling > foo.<Date>(new RegExp)? Sorry, I didn't say that, but what I wrote was confusing on second look. I was simply pointing out that there *will* be a type error, uncaught, which is not the same as an instanceof or 'is' boolean test: (new Date instanceof RegExp) => false, no exception thrown. > Again, I did not mean to use the strict definition of |instanceof|; I > meant some abstract operator (which |instanceof| could be > "upgraded" to) > that does the same thing as |is| except that it works on value exprs. It's not clear (to me at any rate) that you can upgrade instanceof in a backward-compatible fashion, given mutable prototype properties of functions. For the built-ins (which become classes, not constructor functions), you could, and that's what we've done. For user-defined functions that instanceof takes as right operands, there's no need to upgrade beyond how things work as in ES3. But the guarantees with classes are gone: if one changes F.prototype after x = new F, (x instanceof F) could change from true to false. For structural types, we have not tried to upgrade instanceof, mostly because we didn't want to change instanceof much, but also because structural types look like value expressions syntactically. More below. > Allowing |instanceof| to work on classes can trick people into > thinking that |instanceof| can work on type exprs. It might, you're right. On the other hand, we can't keep treating Object or Array as a constructor function, whose name in the global object can be re-bound. That is not even consistent in ES3. See http://wiki.ecmascript.org/doku.php?id=clarification:which_prototype > Yet at the same time, > since the ES3 builtin constructors are now classes, this feature is > required for backwards compatibility. Indeed, and that still seems like the best way forward. But you could be right that we have not upgraded instanceof enough. > If |instanceof| works for classes, then I propose that |instanceof| > also > work for interfaces for the sake of completeness. Getting this to work > is a bit trickier than for classes, since a class can implement > multiple > interfaces or a parent class and multiple interfaces, but it can still > work. This change is perfectly backwards compatible, considering that > interfaces aren't even in ES3. It would involve some algorithm other than the prototype chain walk from x, looking for an object === to y.prototype, for (x instanceof y), since interfaces do not have prototypes. But since (x is I) and (x is J) work fine for x = new C where class C implements I, J {...}, perhaps for interface types we could just make instanceof do what 'is' does. Comments? > That just leaves the question of structural types, which, although I > would like |instanceof| to also work on for the sake of completeness, > doesn't seem worth the effort. As long as structural types are all > defined at compile-time, |is like| is all that's needed. Or even just 'is' -- you don't need 'is like' if you want a type check that demands fixtures matching the structural type's fields. The benefit of 'is' composed with 'like' is when you want a one-time check or assertion, and your code does not need to worry about mutation violating the type a split-second later. From the overview: function fringe(tree) { if (tree is like {left:*, right:*}) { for (let leaf in fringe(tree.left)) yield leaf for (let leaf in fringe(tree.right)) yield leaf } else yield tree } let tree = { left: { left: 37, right: 42 }, right: "foo" } for ( let x in fringe(tree) ) print(x) > Nevertheless, > it would be nice to have a runtime object representing a structural > type, that can then be passed to functions as a normal non-type > argument, and which can then be used somehow to check the type of an > object in a similar matter to |is like|. If structural types could be > created or mutated at runtime like constructors, then this would > obviously become a necessity. Agreed. We have not reflected structural types as values because there's a syntactic ambiguity for array and record types: x instanceof {p:int} x instanceof [int] per ES3 11.8.6, which depends on the [[HasInstance]] internal method (8.6.2), requires throwing a TypeError here (assume int is bound in the scope chain). This is because, for the first example, {p: int} is an object initialiser, not a reflection of a record type, and objects other than functions do not have a [[HasInstance]] internal method. But let's say, per ES3 chapter 16, we extend ES4 so that it allows what would in ES3 be an error if control flow reached either of the above lines for any x (given a binding for int). We've done an analoguos extension for the new operator (see the overview, the section labeled "Record and array types"). ES4 could say "Aha, that looks like an object or array initialiser, but I know it is a structural type expression!" and "upgrade" (x instanceof {p:int}) to (x is {p:int}). Is this worth it? It seemed not to anyone working on this in TG1. We left instanceof alone, but by giving classes prototype objects and making the standard "class constructors" (Object, Date, etc.) be true classes, we kept backward compatibility. Note that making the standard ES3 constructors be classes, we can explain the otherwise ad-hoc difference in ES1-3 between f.prototype for a function f (this property is DontDelete) and C.prototype for a built-in constructor function C (where the property is DontDelete, ReadOnly, and DontEnum). You're right that evolving a language while keeping compatibility makes for more combinations that might want to work together, in some upgraded sense, than if one "minimizes" and leaves the old form (instanceof) alone, but handles the new combinations as well as the old ones in the new form (is). This is a tricky issue, which we've been keenly aware of, but we don't always attend to perfectly -- thanks for keeping after us on it. > There are type expressions and value expressions. Type expressions are > adequately defined on the wiki. The identifiers in type expressions > refer to fixed properties. |class|, |interface|, and |type| statements > all define types and bind those types to fixed properties. Right. > |class| and > |interface| bind runtime class and interface objects, respectively, to > fixed properties. Without getting into runtime vs. compile-time, the above seems better to me if you strike "runtime" from before "class and interface objects". Another way of looking at types, if you do distinguish compile-time and runtime, is that types exist as compile-time values *and* runtime values. Usually the latter are just called "values", but I agree with Lars, who has argued that values exist at compile time too, and "value" is the right word for both, appropriately qualified. Unlike some values which can't be known at compile-time, a type is always a compile-time value and a runtime value. If you buy this, then the sentence cited above could say "bind compile-time class and interface types, which appear at runtime as objects, to fixed properties." Or something like that ;-). Since ES4 does not require an implementation to support strict mode, and since we are trying to avoid requiring analyses that would need a full abstract syntax tree or anything more complicated (control flow graph, SSA) to be built for whole functions, we intentionally want to support the first point of view, that there is no compile- vs. runtime distinction. ES1-3 support this point of view, in the practical sense that its chapter 16 talks about SyntaxError being throwable early (but does not mandate this, i.e., does not mandate compile-time error checking). ES1-3 intentionally allow that implementations may interpret something close to source code, modulo transformations of for and for-in loops, and a few other places that reorder code. There are two passes required to fulfill the obligations of ES3 chapter 10, e.g., defining function-valued properties first based on the parsed function definitions in a program or function body -- but this is not compile-time in the type-checking sense that is optional in ES4. Obviously we are not done proving that ES4 requires no compile-time analyses beyond ES3, but that's our intention. If you see problems in the RI or anywhere else, please do feel free to point them out. > A value expression is the typical ES3 expression with > all the new ES4 syntax extensions, but it can include certain type > expressions in certain circumstances. Type expressions that refer to a > class (or parameterized class) resolve to the runtime class object > (this > is why |let myint = int| isn't a syntax error). Ditto for interfaces. > Type exprs are used in |like|, |is|, |to|, |wrap|, and |cast| > expressions as the second operand (or the only operand in the case of > unary operators). Type expressions are also used as type > annotations in > value expressions. All other cases of type expressions appearing in > value expressions are syntax errors. > > Is that all correct? The unary 'type' operator is there too (I think -- the wiki page cited above is old and the RI is not handling this operator correctly at the moment), but otherwise I think that's correct. >>> At the very least, the differences and similarities need to be >>> fully and >>> carefully documented. ES3 already has plenty of gotchas, and ES4 >>> seems >>> to be introducing plenty more. >> >> It's true that ES4 is introducing optional types. But remember, >> they're optional. You don't have to use them, but if you choose to, >> you need to follow the rules about using type definitions or >> equivalent (class, interface) to make bindings that are fixed >> typenames. >> > > Offtopic rant: TBH, all this talk of the new features being optional > detracts from other issues. Fair enough, but it wasn't "all this talk", it was just one paragraph in my big fat reply :-). My point was that optionality allows for non- orthogonality, and backward compatibility may in fact tie our hands and prevent, e.g., instanceof "upgrades". > Sure, saying a feature is optional is nice > for compatibility and all, but I think more effort should be spent > elaborating on how the new features mesh with the existing features > in a > coherent matter. Any backwards-compatible feature is optional. It > can be > useless or ugly, but hey it's optional. It reinforces the "everything > but kitchen sink" feel of ES4, which is not what you should want to > emphasis. With that said, I'm not saying the type system is > inelegant - > in fact, I consider many aspects of it elegant or nifty - but you > shouldn't dismiss the number of extra gotchas with "hey it's > optional". You're right, and I don't mean to dismiss anything -- more the opposite. The "gotchas" in ES3 may become less grabby, or even go away, if we make the right additions to ES4. Exposing underlying "magic" in the built-ins, I think, and letting programmers use those powers, is one example. In ES3 there's no way to make a constructor function whose prototype property is ReadOnly and DontEnum, e.g. Or even simpler: there is no way to turn on the DontEnum attribute for a property. ES4 adds optional ways to do these things (classes, the new second arg to propertyIsEnumerable). These make ES4, by some counts, have fewer gotchas. But back to type vs. value expressions, which I agree is a new gotcha. But we're not done, and es4-discuss is a huge help in finishing well. So let's keep corresponding. My belief is that merging type and value expressions is right out -- we've accepted solid proposals for structural types, and they mimic the syntax for the related value expressions (functions, initialisers). This leaves us with a grammatical divide between types and values, but special forms such as annotations only take type expressions. So as you point out, the operators such as 'is' and 'instanceof' seem to be where worlds collide visibly. So questions I see include: * Should we "upgrade" instanceof to work like 'is' when given an interface or structural type as its right operand? * Should we allow value expressions on the right of 'is'? We've decided not to already, most recently in September. But it seems to me you are asking for this too. Let me know if I'm missing anything. Thanks, /be _______________________________________________ Es4-discuss mailing list Es4-discuss@mozilla.org https://mail.mozilla.org/listinfo/es4-discuss