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

Reply via email to