need some clarification on compile-time type vs. run-time type

# Yuh-Ruey Chen (18 years ago)

I've been experimenting with the ES4 RI lately. I've discovered that the following doesn't work:

x = int; 10 is x; // error 10 to x; // error 10 cast x; // error var y: x; // error

Of course, if I had used |type x = int|, it would work. Is this because x is not a compile-time type? I find it a bit odd that |10 is x| didn't work while |10 instanceof x| does work, considering that |is| is touted as the successor to the |instanceof| operator. Or is |instanceof| the run-time counterpart to |is|? Then should |instanceof| work on everything that |is| can work on?

These distinctions between compile-time types and these run-time types (e.g. x in the above example) are subtle and confusing. I figure that all these distinctions will trip many ES4 newcomers coming from an ES3 background.

BTW, it doesn't seem like the "like" and "wrap" operators are implemented in the RI yet, so I couldn't really test out structural types.

-Yuh-Ruey Chen

# Graydon Hoare (18 years ago)

Yuh-Ruey Chen wrote:

I've been experimenting with the ES4 RI lately. I've discovered that the following doesn't work:

x = int; 10 is x; // error 10 to x; // error 10 cast x; // error var y: x; // error

Of course, if I had used |type x = int|, it would work. Is this because x is not a compile-time type?

Roughly. Some refinements to terminology: there are no "compile-time types" or "run-time types". There are types (and interfaces, and classes). They get bound to property names. Some of those properties are fixed, some are not. Some fixed properties are additionally constrained to hold types, classes or interfaces. Some are not.

Your example fails because x is not a fixed type-property. Properties introduced by 'type', 'class' or 'interface' are.

Type names in type expressions only consult fixed type-properties, because we want evaluation of type expressions to be as predictable as possible. There are only ever 2 results to evaluating any type expression:

#1 full evaluation to a specific "ground" type term #2 suspended partial evaluation due to incomplete information

The flow of program control may affect which of these two results we get, and it can provide new information to restart a suspended evaluation -- by loading new code containing type definitions using 'eval', for example -- but it cannot invalidate a result of an evaluation. Types never need to be "re-evaluated" to reflect "mutation" in their environment. The evaluation of type expressions can (and should) memoize types.

I find it a bit odd that |10 is x| didn't

work while |10 instanceof x| does work, considering that |is| is touted as the successor to the |instanceof| operator. Or is |instanceof| the run-time counterpart to |is|? Then should |instanceof| work on everything that |is| can work on?

instanceof consults the prototype chain. It happens to be the case that for classes, the prototype chains are arranged to follow the class inheritance hierarchy. But there are a variety of differences; for example, "x instanceof z" may return false in cases where "x is z" returns true (if z is an interface, say).

These distinctions between compile-time types and these run-time types (e.g. x in the above example) are subtle and confusing. I figure that all these distinctions will trip many ES4 newcomers coming from an ES3 background.

Perhaps. It's not clear what else we ought to do; the alternatives we've explored sound worse. Any suggestions?

BTW, it doesn't seem like the "like" and "wrap" operators are implemented in the RI yet, so I couldn't really test out structural types.

No, they are not implemented yet. The implementation of the type system is still somewhat shaky, unfortunately. I am working on it presently.

# Brendan Eich (18 years ago)

On Nov 9, 2007, at 3:20 PM, Graydon Hoare wrote:

Types never need to be "re-evaluated" to reflect "mutation" in their environment. The evaluation of type expressions can (and should) memoize types.

And furthermore, merging type expressions and value expressions, as
Yuh-Ruey seems to want, at the limit means an undecidable type
system. We are not planning to approach anything like that. We want
fixed type terms once open program units that reference them close.

# Yuh-Ruey Chen (18 years ago)

Graydon Hoare wrote:

Yuh-Ruey Chen wrote:

I've been experimenting with the ES4 RI lately. I've discovered that the following doesn't work:

x = int; 10 is x; // error 10 to x; // error 10 cast x; // error var y: x; // error

Of course, if I had used |type x = int|, it would work. Is this because x is not a compile-time type?

Roughly. Some refinements to terminology: there are no "compile-time types" or "run-time types". There are types (and interfaces, and classes). They get bound to property names. Some of those properties are fixed, some are not. Some fixed properties are additionally constrained to hold types, classes or interfaces. Some are not.

Your example fails because x is not a fixed type-property. Properties introduced by 'type', 'class' or 'interface' are.

Type names in type expressions only consult fixed type-properties, because we want evaluation of type expressions to be as predictable as possible. There are only ever 2 results to evaluating any type expression:

#1 full evaluation to a specific "ground" type term #2 suspended partial evaluation due to incomplete information

The flow of program control may affect which of these two results we get, and it can provide new information to restart a suspended evaluation -- by loading new code containing type definitions using 'eval', for example -- but it cannot invalidate a result of an evaluation. Types never need to be "re-evaluated" to reflect "mutation" in their environment. The evaluation of type expressions can (and should) memoize types.

Ah, so that's why I couldn't delete types. I ended up having to restart the RI once the environment got cluttered with all these test types that I defined. I need to get used to declaring them in scopes...

Speaking of which, there's a little bug in the RI: if you try to constrain a property to specific type e.g. |var x: t| (is that the proper terminology?) where t is undefined, then the interpreter correctly reports an error, albeit one that didn't make sense until you explained the "ground" type terminology. However, x remains constrained to this undefined t, so attempts to redeclare x always result in an error (even when trying to reconstrain it to the undefined t again), e.g. |var x: int| results in an "incompatible redefinition of fixture name" error. This bug probably doesn't matter in production, where such errors are supposed to be fatal (I think), but in a REPL interpreter it gets annoying.

I find it a bit odd that |10 is x| didn't

work while |10 instanceof x| does work, considering that |is| is touted as the successor to the |instanceof| operator. Or is |instanceof| the run-time counterpart to |is|? Then should |instanceof| work on everything that |is| can work on?

instanceof consults the prototype chain. It happens to be the case that for classes, the prototype chains are arranged to follow the class inheritance hierarchy. But there are a variety of differences; for example, "x instanceof z" may return false in cases where "x is z" returns true (if z is an interface, say).

Then how would we do a check against an interface bound in a non-fixed property? Is there an expression that does the same thing as |is| except for non-fixed type properties?

The confusion I'm getting is that there seems to be many ways to check or differentiate between types. For example, consider the following ES3 function:

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); }

If t were a fixed type property, then foo could be redefined as:

function foo.<t>(x) { if (!(x is t)) throw some_type_error; print(x); }

Or maybe the following is possible (it's currently disallowed in the RI):

function foo.<t>(x: t) { print(x); }

Which one is preferred in ES4? The ES3 version is more flexible in a way, since it treats types as first-class values, but the last version is the most efficient. Users will have to deal with this choice, but it requires a decent understanding of the type system to make a good choice.

And it doesn't end there. I haven't even addressed the |is like| compound operator, of which there is no counterpart in |instanceof| since structural types apparently can't be stored as non-fixed type properties (|type x={a:int};y=x;| doesn't work in the RI).

These distinctions between compile-time types and these run-time types (e.g. x in the above example) are subtle and confusing. I figure that all these distinctions will trip many ES4 newcomers coming from an ES3 background.

Perhaps. It's not clear what else we ought to do; the alternatives we've explored sound worse. Any suggestions?

I'm not sure how to phrase this, but it seems to me that ES4 is trying to make fixed properties and non-fixed properties (and by extension, type expressions and value expressions, and compile-time features and run-time features) as similar and compatible as possible (e.g. |10 is int| and |10 instanceof int|), yet there are evidently many cases where they can't be interchanged (e.g. my first example). I know from experience that the more similar concepts get, the higher the potential for confusion, until they become the one and the same. And the two concepts in question here cannot be one and the same if we want ES4 to support efficient compilation. Perhaps, to reduce the confusion, the differences between the two can be more pronounced, either through syntax and/or behavior. I don't have any specific suggestions though.

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.

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 9, 2007, at 5:29 PM, Yuh-Ruey Chen wrote:

The confusion I'm getting is that there seems to be many ways to check or differentiate between types. For example, consider the following
ES3 function:

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); }

If t were a fixed type property, then foo could be redefined as:

function foo.<t>(x) { if (!(x is t)) throw some_type_error; print(x); }

Here t is the name of a type parameter to foo, so it is by definition
fixed -- it doesn't matter how you instantiate foo.<T> for some T --

but there again, in the foo.<T> expression, you need a fixed type

term T.

Or maybe the following is possible (it's currently disallowed in
the RI):

function foo.<t>(x: t) { print(x); }

I'll let Graydon reply in full, and give an update -- I heard he
nearly has type params working.

Which one is preferred in ES4? The ES3 version is more flexible in a way, since it treats types as first-class values,

A constructor function is not a type in ES1-3, it's a function
object, which if user-defined has a completely writable prototype
property that instanceof checks. So it is not bad (or good), but I'm
here to say: it's not about types in the ES4 sense.

Indeed user-defined constructor functions all make Object instances,
by definition, although you could strengthen them to make structural
subtypes of Object in ES4:

function MyConstructor(a, b) { return {a: a, b: b} : {a: int, b: string}; }

but the last version is the most efficient. Users will have to deal with this choice,
but it requires a decent understanding of the type system to make a good
choice.

Users can buy by the yard. The old ways work for the dynamic
constructor/prototype world everyone knows.

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).

And it doesn't end there. I haven't even addressed the |is like| compound operator,

It's not a compound operator: 'like' is a type constructor or type
unary operator if you prefer: like T is a type, you can use it
freely. Thus because (x is T) can be tested, and T can be defined as
like U, you can write (x is like U). Make sense?

of which there is no counterpart in |instanceof| since structural types apparently can't be stored as non-fixed type properties (|type x={a:int};y=x;| doesn't work in the RI).

Type are types, not functions; the instanceof right operand is a
function per ES1-3 and backward compatibility.

I'm not sure how to phrase this, but it seems to me that ES4 is trying to make fixed properties and non-fixed properties (and by extension, type expressions and value expressions, and compile-time features and run-time features) as similar and compatible as possible (e.g. |10 is int| and |10 instanceof int|), yet there are evidently many cases
where they can't be interchanged (e.g. my first example).

I think you are mixing up fixed and non-fixed properties with types
and functions.

I know from experience that the more similar concepts get, the higher the
potential for confusion, until they become the one and the same. And the two concepts in question here cannot be one and the same if we want ES4 to support efficient compilation. Perhaps, to reduce the confusion, the differences between the two can be more pronounced, either through syntax and/or behavior. I don't have any specific suggestions though.

Here's what should work:

10 is int => true

10 instanceof int => true

type T = int 10 is T => true

10 instanceof T => true

No confusion, so far (IIRC the RI has a bug on the last line, but
let's assume it is fixed). Now:

let U = int 10 instanceof U => true

10 is U => error

Using a const (or let const) does not help here, only type will do.

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 optiona. 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.

One escape hatch is to use reflection, which looks something like

{ use namespace reflect print(typeOf(10).isSubtypeOf(U)) }

That's not working for me in the RI, and I may have misremembered a
method name. Graydon knows best.

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

On Nov 9, 2007, at 5:29 PM, Yuh-Ruey Chen wrote:

The confusion I'm getting is that there seems to be many ways to check or differentiate between types. For example, consider the following
ES3 function:

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); }

If t were a fixed type property, then foo could be redefined as:

function foo.<t>(x) { if (!(x is t)) throw some_type_error; print(x); }

Here t is the name of a type parameter to foo, so it is by definition
fixed -- it doesn't matter how you instantiate foo.<T> for some T --
but there again, in the foo.<T> expression, you need a fixed type
term T.

That's what I meant. If t could never be non-fixed, i.e. it's a type alias, class name, or other type expr, then type parameters could be used. But if, for example, t is a constructor or is otherwise computed at runtime, it couldn't be used as a type argument.

Or maybe the following is possible (it's currently disallowed in
the RI):

function foo.<t>(x: t) { print(x); }

I'll let Graydon reply in full, and give an update -- I heard he
nearly has type params working.

Which one is preferred in ES4? The ES3 version is more flexible in a way, since it treats types as first-class values,

A constructor function is not a type in ES1-3, it's a function
object, which if user-defined has a completely writable prototype
property that instanceof checks. So it is not bad (or good), but I'm
here to say: it's not about types in the ES4 sense.

Indeed user-defined constructor functions all make Object instances,
by definition, although you could strengthen them to make structural
subtypes of Object in ES4:

function MyConstructor(a, b) { return {a: a, b: b} : {a: int, b: string}; }

Ah, so expressions can be annotated with types? Didn't see that on the wiki and it's not implemented in the RI yet.

but the last version is the most efficient. Users will have to deal with this choice,
but it requires a decent understanding of the type system to make a good
choice.

Users can buy by the yard. The old ways work for the dynamic
constructor/prototype world everyone knows.

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.

What's the rationale behind not throwing a type error when calling foo.<Date>(new RegExp)? I'm looking at the type_parameters page in the

wiki and I'm not seeing anything... It's odd that this would not throw an exception while the following would:

function bar(x: int) { print(x); } bar("hi");

And it doesn't end there. I haven't even addressed the |is like| compound operator,

It's not a compound operator: 'like' is a type constructor or type
unary operator if you prefer: like T is a type, you can use it
freely. Thus because (x is T) can be tested, and T can be defined as
like U, you can write (x is like U). Make sense?

Yes indeed, thanks.

of which there is no counterpart in |instanceof| since structural types apparently can't be stored as non-fixed type properties (|type x={a:int};y=x;| doesn't work in the RI).

Type are types, not functions; the instanceof right operand is a
function per ES1-3 and backward compatibility.

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.

The line between types and constructors is somewhat blurred by allowing |instanceof| to work on class objects, which I would hardly consider constructors, if not for the fact that they have an intrinsic::construct method. Allowing |instanceof| to work on classes can trick people into thinking that |instanceof| can work on type exprs. Yet at the same time, since the ES3 builtin constructors are now classes, this feature is required for backwards compatibility.

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.

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. 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.

I'm not sure how to phrase this, but it seems to me that ES4 is trying to make fixed properties and non-fixed properties (and by extension, type expressions and value expressions, and compile-time features and run-time features) as similar and compatible as possible (e.g. |10 is int| and |10 instanceof int|), yet there are evidently many cases
where they can't be interchanged (e.g. my first example).

I think you are mixing up fixed and non-fixed properties with types
and functions.

Probably :) This is all relatively new terminology to me. This is how I currently understand 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. |class| and |interface| bind runtime class and interface objects, respectively, to fixed properties. 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?

I know from experience that the more similar concepts get, the higher the
potential for confusion, until they become the one and the same. And the two concepts in question here cannot be one and the same if we want ES4 to support efficient compilation. Perhaps, to reduce the confusion, the differences between the two can be more pronounced, either through syntax and/or behavior. I don't have any specific suggestions though.

Here's what should work:

10 is int => true 10 instanceof int => true type T = int 10 is T => true 10 instanceof T => true

No confusion, so far (IIRC the RI has a bug on the last line, but
let's assume it is fixed). Now:

let U = int 10 instanceof U => true 10 is U => error

Using a const (or let const) does not help here, only type will do.

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 optiona. 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. 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".

One escape hatch is to use reflection, which looks something like

{ use namespace reflect print(typeOf(10).isSubtypeOf(U)) }

That's not working for me in the RI, and I may have misremembered a
method name. Graydon knows best.

/be

I know that typeOf is defined in the intrinsic namespace. intrinsic::isSubtypeOf doesn't seem to be implemented yet.

  • Yuh-Ruey Chen
# Graydon Hoare (18 years ago)

Yuh-Ruey Chen wrote:

I'm not sure how to phrase this, but it seems to me that ES4 is trying to make fixed properties and non-fixed properties (and by extension, type expressions and value expressions, and compile-time features and run-time features) as similar and compatible as possible (e.g. |10 is int| and |10 instanceof int|), yet there are evidently many cases
where they can't be interchanged (e.g. my first example).

I think you just said it very well. This is an excellent articulation of the problem you're perceiving.

Probably :) This is all relatively new terminology to me. This is how I currently understand 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. |class| and |interface| bind runtime class and interface objects, respectively, to fixed properties. 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?

Yes.

It is worth keeping in mind that -- unfortunately! -- we must decide ahead of time whether we wish to make an operand a type expression or a value expression. Neither is a proper syntactic subset of the other, though an identifier can occur in each, as can the parameter-apply operator ".<>".

So for example we have "<vexpr> is <texpr>", not "<vexpr> is <vexpr>".

This might be the source of some misery, I'll grant. More curiously, in the value-expression context .<> takes value-expression arguments;

whereas in the type-expression context .<> takes type-expression

arguments. This too might tickle your sense of "too similar to be different".

One alternative is to mark type expression operands for these operators explicitly: define the "is" operator as taking two value expressions

# Brendan Eich (18 years ago)

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

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:

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

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,

# Graydon Hoare (18 years ago)

Brendan Eich wrote:

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.

Fixed earlier this week on the trunk. Sorry it's not in the public build at the moment. My hope is that we can get rolling snapshot builds off the trunk happening at some point soon.

# Michael O'Brien (18 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20071110/27df7a1d/attachment-0002

# Brendan Eich (18 years ago)

See bugs.ecmascript.org/ticket/248 -- the meta objects proposal:

proposals:meta_objects

used intrinsic, but as #248 notes, there are good reasons for putting
the meta-objects functions and types in their own namespace:

 * intrinsic should mean "early bindable, generally useful"
 * meta is reserved for hooks (and that seems like a good thing)
 * iterator is used for all the iteration stuff
 * uint32ops is used for uint operators
 * reflect is a good name for reflection
 * partitioning the methods means that we can have reflection  

methods on various kinds of objects without worrying about name
clashes (e.g., reflect::construct could in principle be a method on a
class object, so class objects could implement ClassType directly --
this is possible at present, but if so precludes adding an
instrinsic::construct method to the class object for other purposes
in the future)

The last point is significant.

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

On Nov 9, 2007, at 11:24 PM, Yuh-Ruey Chen wrote:

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.

I see. I wonder if this can somehow be extended so that it works on non-structural types as well. For example, to initialize a Map, you could use {a: 0, b: 1, c: 2} : Map, instead of Map({a:0, b:1, c:2}). Perhaps it would map onto the |meta static function invoke| method. The downside of this syntax sugar though, is that it hides the cost of the creation of the object literal, fooling the user into thinking that the Map is directly initialized from the literal.

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:

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 ;-).

I like this revised syntax: |a is type b| is much clearer than |a is b|, which a newbie can mistake for object equality, especially if he comes from a Python background. On purely aesthetic grounds though (ignoring the type vs. value expr distinction), I think |isa| would be even better, but on the other hand, |isa like| sounds like something Mario would say.

Can you elaborate on the "earlier and more certain type checking"? I thought you said for |is| checks (along with |instanceof| checks) there can be no early type errors, so that leaves |is| to be a purely runtime check.

Anyway, we still have a similar confusion to what we had with |instanceof|, except this time it's all in |is|:

// assume |b| is a class and |a| is an instance of it a is type b a is b

Since both work, this can fool the user into thinking that the |type| operator is optional. Furthermore, the latter can still lead to newbie confusion (thinking that the expr is equivalent to |a == b|). So I'm not sure there is a net win.

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.

Well, I was just suggesting adding additional functionality to |instanceof|, not changing the original behavior of it, so I'm not sure where the compatibility issue is coming into play.

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 works for me.

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.

How about |obj instanceof type {a: int}|?

This makes me wonder: what exactly does |type x| in a value expr resolve to? If it "resolves to a type", that what does that exactly mean? For type expr nested within a value expr, that implies to me that it resolves to a runtime representation of that type. And that implies that every type must have a runtime representation, including structural types, e.g. |type {a: int}| would resolve to some runtime representation of the structural type defined by |{a: int}|.

But from what I've heard and read up to now, |type x| isn't as generic as I'm implying above, so I'm confused.

In any case, one advantage of having a runtime representation for every type is that structural types can now be more easily used in a ES3-style without the usage of type parameters and also allow type bindings to be computed at runtime, e.g.

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); } ... let t; if (cond) // note how t can only be determined at runtime here t = type {a: int}; else t = type like {a: double}; ... foo(obj, t);

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.

No problem :)

|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.

Alright, that makes sense. I'll stick with the terminology "early type checking" in the future to avoid all this semantic confusion.

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.

You're right, ES4 does fix some gotchas in ES3.

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

I'm going to try to order my thoughts by listing the possible type testing functionalities and how they map to operators.

(a) object ISA class or interface (O(1), early test) (b) object's class is a superset of a structural type (O(1), early test) (c) object is compatible with a structural type (O(n) where n=# props in structural type, late test) (d) object's prototype chain includes the prototype of another object (O(n) where n=length of chain, late test) (e) object ISA x, where x is determined at runtime to be a class or interface (O(1), late test) (f) object's class is a superset of x, where x is determined at runtime to be a structural type (O(1), late test) (g) object is compatible with x, where x is determined at runtime to be a structural type (same as (c))

|is| does (a), (b) |is like| does (c) |instanceof| does (d) |instanceof| does (e) for classes nothing does (e) for interfaces nothing does (f) and (g) since there's no "runtime structural type"

The asymptotic times don't really matter - only whether the test can be performed early does. If something can be tested early, that implies that it has to be a type expr. Late testing implies value exprs. However, although |is| seems to be capable of doing early testing, if no type exceptions are thrown early for |is|, then it is in effect a purely runtime check, right?

Furthermore, the syntax of type exprs and value exprs are incompatible (neither can be subset of each other) because of the following issues (AFAIK):

  1. structural type syntax of type expr collides with object and array literal syntax of value expr
  2. union type syntax collides with comma expr syntax of value expr (e.g. |(1,2)|)
  3. type parameter syntax of type expr collides with type argument syntax of value expr

I've been scratching my head for a good proposal that coherently distinguishes |is| and |instanceof| and the associated syntax, but I've restarted several times already with no solution. Now I've begun to wonder why the two operators can't be unified in some way, considering that both are runtime checks. If every type had a runtime representation then this could work (see example I gave above for structural types).

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 10, 2007, at 9:03 PM, Yuh-Ruey Chen wrote:

I see. I wonder if this can somehow be extended so that it works on non-structural types as well. For example, to initialize a Map, you could use {a: 0, b: 1, c: 2} : Map, instead of Map({a:0, b:1, c:2}). Perhaps it would map onto the |meta static function invoke| method.
The downside of this syntax sugar though, is that it hides the cost of the creation of the object literal, fooling the user into thinking that
the Map is directly initialized from the literal.

Yeah, it doesn't seem good judging by both hidden costs and redundant
features. Map has a constructor function and a meta static function
invoke -- which does exactly what you wonder about above:

     meta static function invoke(x : Object!) {
         let d = new Map.<EnumerableId,V>;
         for ( let n in x )
             if (x.hasOwnProperty(n))
                 d.put(n, x[n]);
         return d;
     }

This means writing

 let map: Map.<string, *> = {word1: val1, ... wordN: valN};

does exactly what everyone wants from a "Dictionary" (see the old
threads on this list under that name). Bonus implementation points
for optimizing away the object initialiser's Object instance, but not
a worry to over-specify.

Can you elaborate on the "earlier and more certain type checking"? I thought you said for |is| checks (along with |instanceof| checks)
there can be no early type errors, so that leaves |is| to be a purely
runtime check.

There are questions about exactly what strict mode analyses might be
mandated as normative if an implementation supports strict mode. This
may be clearer to Graydon and Cormac than to me, so I'd welcome their
comments. For example:

 function g(a_not_null: T!) ...

 function f(a: T) {
     if (a !== null)
         g(a);           // no cast required?
 }

Now consider changing the condition from (a !== null) to (a is T!).
Should strict mode allow the above, generating a runtime check for
null when g is called? Should it require (a cast T!)? Should it
figure things out with fancier analysis (this example is trivial, but
it is of the same kind as much harder-to-analyze ones). We have said
"cast required" before, but the question came up again recently.

Anyway, we still have a similar confusion to what we had with |instanceof|, except this time it's all in |is|:

// assume |b| is a class and |a| is an instance of it a is type b a is b

Since both work, this can fool the user into thinking that the |type| operator is optional. Furthermore, the latter can still lead to newbie confusion (thinking that the expr is equivalent to |a == b|). So
I'm not sure there is a net win.

I think we should separate the Python n00b concern that 'is' might be
taken for ===. That is not a big potential problem in my view; JS is
not Python, even with things like generators (we don't have
GeneratorExit, names differ for the iteration protocol hooks, etc.)
in the mix. You have to be this tall; you have to pay attention to
the basics. False cognates in different parts of Europe can cause
embarrassment or even legal troubles ;-).

The two-edged nature of the type/value expression split means, as you
say, someone's naive expectation that there is only one expression
grammar in the language will be shattered, some of the time. This is
the problem to fix, or mitigate as best we can.

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.

Well, I was just suggesting adding additional functionality to |instanceof|, not changing the original behavior of it, so I'm not
sure where the compatibility issue is coming into play.

Yes, you're upgrading it, but now it makes stronger guarantees for
the upgrade-case inputs than for the old ones. That seems problematic.

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 works for me.

Besides changing the guarantees, it mixes domains: prototype chain
and (possibly mutable) function.prototype vs. class/interface
supertype vs. structural supertype.

How about |obj instanceof type {a: int}|?

Sure, that follows, but it's unnecessary for backward compatibility
as I noted. It might be good for clarity, until everyone learned it
and got tired of typing it too much ;-).

This makes me wonder: what exactly does |type x| in a value expr
resolve to? If it "resolves to a type", that what does that exactly mean?

The RI alas has this in eval.sml:

and evalTypeExpr (regs:Mach.REGS) (te:Ast.TYPE_EXPR) : Mach.VAL = case te of Ast.SpecialType st => Mach.Null (* FIXME ) | Ast.UnionType ut => Mach.Null ( FIXME ) | Ast.ArrayType at => Mach.Null ( FIXME *) | Ast.TypeName tn => evalExpr regs (Ast.LexicalRef { ident=tn,

loc=NONE }) | Ast.FunctionType ft => Mach.Null (* FIXME ) | Ast.ObjectType ot => Mach.Null ( FIXME ) | Ast.NullableType { expr, nullable } => Mach.Null ( FIXME ) | Ast.InstanceType { ty, ... } => Mach.Null ( FIXME *)

but I believe the intention is to evaluate to the appropriate type
meta-object.

For type expr nested within a value expr, that implies to me that it resolves to a runtime representation of that type.

Right -- type meta-objects are discussed here:

proposals:meta_object

See also:

bugs.ecmascript.org/ticket/248

And that implies that every type must have a runtime representation, including structural types, e.g. |type {a: int}| would resolve to some runtime
representation of the structural type defined by |{a: int}|.

Indeed :-).

But from what I've heard and read up to now, |type x| isn't as generic as I'm implying above, so I'm confused.

If I understand your point, it's that if we allow

(a is T)

given a type name T bound by a type, class, or interface definition;
but we disallow

let (t = T) (a is t)

and insist on

let (t = T) (a is type t)

where (type t) evaluates at runtime to the meta-object for T and then
'is' proceeds to test whether a's dynamic type is a subtype of T,
then what good does our restriction on 'is' (that its right operand
must be a type fixture) actually do?

If I'm following myself, then all I can say is: I hear you! I'm with
you.

In any case, one advantage of having a runtime representation for
every type is that structural types can now be more easily used in a ES3- style without the usage of type parameters and also allow type bindings
to be computed at runtime, e.g.

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); } ... let t; if (cond) // note how t can only be determined at runtime here t = type {a: int}; else t = type like {a: double}; ... foo(obj, t);

We went down this road before including type parameters, hoping to
avoid them while saving decidability. Yes, you can write code like
the above, but it is not the same when foo uses t in a type
annotation (say x:t in the parameter list). There, t has to be a
distinguished type parameter, so the checker can see instantiations
and make sure they pass fixed types too. There's also alpha renaming
to consider: type params should not collide unhygienically.

You're right, ES4 does fix some gotchas in ES3.

I certainly hope so -- I'll dance a jig at the end of the day if this
is clear to everyone.

I'm going to try to order my thoughts by listing the possible type testing functionalities and how they map to operators.

(a) object ISA class or interface (O(1), early test) (b) object's class is a superset of a structural type (O(1), early
test) (c) object is compatible with a structural type (O(n) where n=#
props in structural type, late test) (d) object's prototype chain includes the prototype of another object (O(n) where n=length of chain, late test) (e) object ISA x, where x is determined at runtime to be a class or interface (O(1), late test) (f) object's class is a superset of x, where x is determined at
runtime to be a structural type (O(1), late test) (g) object is compatible with x, where x is determined at runtime
to be a structural type (same as (c))

|is| does (a), (b)

Right.

|is like| does (c)

Nit: your definition for (c) is not using "compatible with structural
type" the way we define the type compatibility relation (written "~:"
instead of "<:", see doku.php? id=spec:type_relations). We want soundness by some definition, so (a
is T) must mean that a cannot mutate a split-second later to violate
T. But let's say your item (b) covers that.

|instanceof| does (d) |instanceof| does (e) for classes nothing does (e) for interfaces

The meta-object stuff can be used:

use namespace reflect typeOf(object).isSubtypeOf(iface)

or something like that.

nothing does (f) and (g) since there's no "runtime structural type"

No, meta-objects again.

The asymptotic times don't really matter - only whether the test
can be performed early does. If something can be tested early, that implies that it has to be a type expr. Late testing implies value exprs. However, although |is| seems to be capable of doing early testing,
if no type exceptions are thrown early for |is|, then it is in effect a
purely runtime check, right?

Yes, modulo strict mode analyses TBD (see above).

Furthermore, the syntax of type exprs and value exprs are incompatible (neither can be subset of each other) because of the following issues (AFAIK):

  1. structural type syntax of type expr collides with object and array literal syntax of value expr
  2. union type syntax collides with comma expr syntax of value expr
    (e.g. |(1,2)|)
  3. type parameter syntax of type expr collides with type argument
    syntax of value expr

You've got it.

I've been scratching my head for a good proposal that coherently distinguishes |is| and |instanceof| and the associated syntax, but
I've restarted several times already with no solution. Now I've begun to wonder why the two operators can't be unified in some way, considering that both are runtime checks. If every type had a runtime
representation then this could work (see example I gave above for structural types).

The issues are (AFAIK):

  • Should instanceof do its loosey-goosey ES3 thing for functions,
    which have mutable .prototype, and mix this backward-compatible
    feature into different-in-many-ways subtype tests done by 'is'-as- proposed?

  • Should 'is' insist on fixed type name in its right operand, or is
    this inconsistent and pointless, an RI bug even?

I'm going to summon Graydon and stand back now.

# Brendan Eich (18 years ago)

On Nov 10, 2007, at 9:57 PM, Brendan Eich wrote:

If I understand your point, it's that if we allow

(a is T)

given a type name T bound by a type, class, or interface definition; but we disallow

let (t = T) (a is t)

and insist on

let (t = T) (a is type t)

Make that

let (t = type T) (a is type t)

But even that doesn't work. I forgot an agreement from the September
face-to-face meeting, to make 'is' less future-hostile to an "ES5"
that merges type and value expressions somehow: change 'is' to insist
on a type expression only, therefore not including the unary 'type E'
vaue-expression.

This results in the current RI's behavior, modulo that bug where
'type T' => null (those FIXME comments in eval.sml):

type T = {p:int} let t = T [locn] /Users/brendaneich/Hacking/es4/com.mozilla.es4.smlnj/builtins/ Error.es:86:55-86.55 [stack] [init t()] ERROR EvalError: uncaught exception: TypeError: getValue on a
type property: <public >::T (near /Users/brendaneich/Hacking/es4/

com.mozilla.es4.smlnj/builtins/Error.es:86:55-86.55)

let t = type T t

null

o = {p:42} [object Object]

o is T

false

q = {p:42}:T [object Object]

q is T

true

q is t ERROR VerifyError: expected type fixture for: [[<public >,
<internal >], [<public ES4>]]::t (near <no filename>:1:2-1.1)

while normalizing <TypeName: {(<public >, <internal >), (<public

ES4>)}::t> (near <no filename>:1:2-1.1)

q is type t ERROR ParseError: expecting semicolon before identifier(t) (near
<no filename>:1:2-1.1)

q is (type t) ERROR ParseError: unknown token in unionType (near <no filename>:

1:2-1.1)

See, 'is' won't even begin to parse 'type t' or parenthesized
(union!) forms of same. The RI bug that made t null doesn't matter.

The issues are (AFAIK):

  • Should instanceof do its loosey-goosey ES3 thing for functions, which have mutable .prototype, and mix this backward-compatible feature into different-in-many-ways subtype tests done by 'is'-as- proposed?

My current thinking is "no" but I'd like to think more.

  • Should 'is' insist on fixed type name in its right operand, or is this inconsistent and pointless, an RI bug even?

So it's not an RI bug (ignoring the lack of meta-object hookup). It's
intentional future-proofing against the day when merging type and
value expressions (somehow!) is upon us. Which you've expedited! ;-)

If we allow a value expression on the right of 'is', the only
syntactic ambiguities are structural type expressions:

record: {p:int, q:string} array: [boolean, double, string] union: (undefined, string) function: function (A,B):C /* no body, I'm a type */

Requiring these to be prefixed by unary 'type' and committing to the
consequences (no strict-mode checking of 'is' by fancy, even
conservative analyses; no future where we merge type and value
expressions differently) does seem better to me. But I'm forgetting
something from that September meeting, maybe. Checking minutes...

doku.php? id=meetings:minutes_sep_27_2007#proposal_walkthrough

Ticket 103: bugs.ecmascript.org/ticket/103

Seems the resolution ruled narrowly by pointing to meta-objects, as
I've done a couple of times in this thread. But the big picture
confusion you point out, that users expect dyadic operators to take
value expression operands, and that fixed type names vs. other names,
even well-bound let variable names (let const even!) can't be used on
the right of 'is', seems not to have been considered.

If I nerd out over types, yeah: is/cast/wrap all take a right operand
which must be a type expression. Get used to it, poindexter! ;-) But
really, it's a bit harsh and anti-JS to say this, for 'is' at any
rate. Possibly the verb is too short and overloaded. More work
needed, I think.

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

I'm going to summon Graydon and stand back now.

/be

Right, I'll wait to reply in full until then. I just want to point out that the spec namespace isn't public. spec:spec gives me a "permission denied" page.

# P T Withington (18 years ago)

On 2007-11-11, at 00:57 EST, Brendan Eich wrote:

There are questions about exactly what strict mode analyses might be mandated as normative if an implementation supports strict mode. This may be clearer to Graydon and Cormac than to me, so I'd welcome their comments. For example:

function g(a_not_null: T!) ...

function f(a: T) {
    if (a !== null)
        g(a);           // no cast required?
}

IMO, mandating that level of analysis violates the spirit of the goal
of permitting simple compilers. You should use type switch if you
want to avoid the cast (and guarantee no redundant runtime overhead).

# Brendan Eich (18 years ago)

The spec: namespace holds old, misleadingly inaccurate (for ES4) docs
based on AS3. Nothing there has been touched in many moons. We made
it inaccessible to avoid repeated confusion, and sure enough, when it
was reopened inadventently again for a few days, almost immediately
there was confusion.

We could just remove spec: at some point. The plan is not to use
dokuwiki for actual spec production, since it isn't rich enough to
generate the final Ecma or ISO spec format.

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

Anyway, we still have a similar confusion to what we had with |instanceof|, except this time it's all in |is|:

// assume |b| is a class and |a| is an instance of it a is type b a is b

Since both work, this can fool the user into thinking that the |type| operator is optional. Furthermore, the latter can still lead to newbie confusion (thinking that the expr is equivalent to |a == b|). So
I'm not sure there is a net win.

I think we should separate the Python n00b concern that 'is' might be
taken for ===. That is not a big potential problem in my view; JS is
not Python, even with things like generators (we don't have
GeneratorExit, names differ for the iteration protocol hooks, etc.)
in the mix. You have to be this tall; you have to pay attention to
the basics. False cognates in different parts of Europe can cause
embarrassment or even legal troubles ;-).

Okay, agreed.

Well, I was just suggesting adding additional functionality to |instanceof|, not changing the original behavior of it, so I'm not
sure where the compatibility issue is coming into play.

Yes, you're upgrading it, but now it makes stronger guarantees for
the upgrade-case inputs than for the old ones. That seems problematic.

Sorry, I'm still not getting it. The upgraded |instanceof| will behave exactly the same for the inputs non-upgraded |instanceof| works on. There are new guarantees, but they in no way affect the original ones. There's no way to change the prototype chain of an object instantiated from a class, right? |obj instanceof klass| is practically equivalent to |obj is klass|. Am I missing something?

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 works for me.

Besides changing the guarantees, it mixes domains: prototype chain
and (possibly mutable) function.prototype vs. class/interface
supertype vs. structural supertype.

How about |obj instanceof type {a: int}|?

Sure, that follows, but it's unnecessary for backward compatibility
as I noted. It might be good for clarity, until everyone learned it
and got tired of typing it too much ;-).

I'm not exactly advocating upgrading |instanceof|. I just want |is| and |instanceof| to be coherent on some fashion, so that the user can easily choose one or the other with as little confusion as possible. More on this later.

For type expr nested within a value expr, that implies to me that it resolves to a runtime representation of that type.

Right -- type meta-objects are discussed here:

proposals:meta_object

See also:

bugs.ecmascript.org/ticket/248

And that implies that every type must have a runtime representation, including structural types, e.g. |type {a: int}| would resolve to some runtime
representation of the structural type defined by |{a: int}|.

Indeed :-).

Ah, that clarifies things a lot.

But from what I've heard and read up to now, |type x| isn't as generic as I'm implying above, so I'm confused.

If I understand your point, it's that if we allow

(a is T)

given a type name T bound by a type, class, or interface definition;
but we disallow

let (t = T) (a is t)

and insist on

let (t = T) (a is type t)

where (type t) evaluates at runtime to the meta-object for T and then
'is' proceeds to test whether a's dynamic type is a subtype of T,
then what good does our restriction on 'is' (that its right operand
must be a type fixture) actually do?

Yes, that's one of the reason I'm confused. I didn't mention it explicitly, because the way Graydon replied seemed to imply to me that there was a very good reason that |is| requires type expressions, but on second look, that's not the case. The other reason I was confused was because of RI bugs, which you've elaborated on. When I saw that the expr |(type int)| in the RI was not working, it made me think |type| was some special operator that can only be used in certain situations.

Actually the fixed example you gave (|let (t = type T) (a is type t)|) still confuses me. The first |type T| accepts a type expr and evaluates to a meta-object, binding it to |t|. Now there are two possible interpretations for the |is| test:

  1. |is| expects a type expr, so |type t| is evidently a type expr. So this is the reverse of the previous usage of the |type| operator, i.e. this |type t| accepts a meta-object and evaluates to type expr. If this interpretation is correct, this is extremely confusing.
  2. |is| expects either a type expr or a meta-object, but in the latter case, that meta-object must be from a |type| expr. This is a weird restriction. Furthermore, I though the |x| in |type x| had to be a type expr, yet in this case, |x| is a value expr (evaluating to the meta-object). Again, very confusing.

So again, how exactly is |type| supposed to work?

If I'm following myself, then all I can say is: I hear you! I'm with
you.

In any case, one advantage of having a runtime representation for
every type is that structural types can now be more easily used in a ES3- style without the usage of type parameters and also allow type bindings
to be computed at runtime, e.g.

function foo(x, t) { if (!(x instanceof t)) throw some_type_error; print(x); } ... let t; if (cond) // note how t can only be determined at runtime here t = type {a: int}; else t = type like {a: double}; ... foo(obj, t);

We went down this road before including type parameters, hoping to
avoid them while saving decidability. Yes, you can write code like
the above, but it is not the same when foo uses t in a type
annotation (say x:t in the parameter list). There, t has to be a
distinguished type parameter, so the checker can see instantiations
and make sure they pass fixed types too. There's also alpha renaming
to consider: type params should not collide unhygienically.

Yeah, ES4 type parameters and type exprs allow early type checking, but I figured that it would be nice to be able to use some ES4 features without requiring the usage of the whole shebang. Namely, I can imagine a user coming from an ES3 background to instantly see the usefulness of structural types, yet not care at all about classes, type parameters, or early type checking.

Nit: your definition for (c) is not using "compatible with structural
type" the way we define the type compatibility relation (written "~:"
instead of "<:", see doku.php? id=spec:type_relations).

BTW, I was mentioning the issue of the spec namespace being non-public because I couldn't access this page.

Brendan Eich wrote:

The issues are (AFAIK):

  • Should instanceof do its loosey-goosey ES3 thing for functions, which have mutable .prototype, and mix this backward-compatible feature into different-in-many-ways subtype tests done by 'is'-as- proposed?

My current thinking is "no" but I'd like to think more.

  • Should 'is' insist on fixed type name in its right operand, or is this inconsistent and pointless, an RI bug even?

So it's not an RI bug (ignoring the lack of meta-object hookup). It's
intentional future-proofing against the day when merging type and
value expressions (somehow!) is upon us. Which you've expedited! ;-)

If we allow a value expression on the right of 'is', the only
syntactic ambiguities are structural type expressions:

record: {p:int, q:string} array: [boolean, double, string] union: (undefined, string) function: function (A,B):C /* no body, I'm a type */

Requiring these to be prefixed by unary 'type' and committing to the
consequences (no strict-mode checking of 'is' by fancy, even
conservative analyses; no future where we merge type and value
expressions differently) does seem better to me. But I'm forgetting
something from that September meeting, maybe. Checking minutes...

doku.php? id=meetings:minutes_sep_27_2007#proposal_walkthrough

Ticket 103: bugs.ecmascript.org/ticket/103

Seems the resolution ruled narrowly by pointing to meta-objects, as
I've done a couple of times in this thread. But the big picture
confusion you point out, that users expect dyadic operators to take
value expression operands, and that fixed type names vs. other names,
even well-bound let variable names (let const even!) can't be used on
the right of 'is', seems not to have been considered.

If I nerd out over types, yeah: is/cast/wrap all take a right operand
which must be a type expression. Get used to it, poindexter! ;-) But
really, it's a bit harsh and anti-JS to say this, for 'is' at any
rate. Possibly the verb is too short and overloaded. More work
needed, I think.

/be

Hmm, I can see why it kind of makes sense for |is|, |cast|, and |wrap| all to require type exprs, considering they are all type operators. But since |is| is a purely runtime check (pending Graydon's reply), it makes no sense for it to not accept value exprs.

So far I see a couple solutions:

  1. Let |is| work on value exprs, but leave |instanceof| alone. Then the difference between the two resolves to whether the operand has a prototype chain. That is, the only inputs that both will work on are classes. It's still a gotcha, but a minor one.

  2. Unify |is| and |instanceof| into a single |instanceof| operator. I'm still not sure what you mean by guarantees, so at the moment I don't see an issue other than |is| currently requiring a type expr. I also just learned from your post that AS3 already has an |is| operator that works similarly, so that may be another barrier to this solution. |instanceof| composed with |like| also sounds awkward, e.g. |instanceof like type {a: int}|, unless it's the other way around, e.g. |instanceof type like {a: int}|.

  3. Let |is| work on value exprs, and upgrade |instanceof| to work on everything |is| works on. Again, not sure what you mean be guarantees. This solution doesn't require the removal of |is| and so is more compatible with AS3. On the other hand, the fewer keywords, the better.

  4. As you say (I think), |is| may be too overloaded. That it works both classes/interfaces and structural types even though the two are very distinct tests is a cause for concern. Yet it is convenient to group them into one operator.

In addition to all that, if |is| works on value exprs, there's still the confusion I've previously pointed out:

// |b| is a class, |a| is instance of |b| a is type b a is b

Maybe we can just explain to the user that |type| is only required for structural types to avoid syntactical ambiguity (in the same vein as block {} vs. object literal {})?

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 11, 2007, at 3:09 PM, Yuh-Ruey Chen wrote:

Sorry, I'm still not getting it. The upgraded |instanceof| will behave exactly the same for the inputs non-upgraded |instanceof| works on. There are new guarantees, but they in no way affect the original ones.

They're stronger than the original ones, and different in kind too.
Seems like apples and oranges mixed under one operator. Not obviously
necessary given 'is', but here's a thought: instanceof does its
prototype chain walk on the left operand, looking for an object equal
to the value of the right operand's 'prototype' property, iff the
right operand has [[HasInstance]] as function objects do in ES3.
Otherwise it does a dynamic form of 'is', and for syntactic
disambiguation, you may have to use unary 'type' on the right operand.

There's no way to change the prototype chain of an object instantiated from a class, right? |obj instanceof klass| is practically
equivalent to |obj is klass|. Am I missing something?

No, classes are not really the issue. The key difference is that
instanceof results can change over time for a given object on the
left and plain old function on the right, while 'is' results cannot
vary over the lifetimes of the same operands.

Yes, that's one of the reason I'm confused. I didn't mention it explicitly, because the way Graydon replied seemed to imply to me that there was a very good reason that |is| requires type expressions,
but on second look, that's not the case.

See my followup mail, and bugs.ecmascript.org/ticket/103 --
Graydon and the RI agreed with the last decision, from September -- I
had forgotten about the exclusion of 'type E' to escape from value
syntax on the right of 'is', in favor of making 'is' require a type
expression only on its right.

Actually the fixed example you gave (|let (t = type T) (a is type t)|) still confuses me. The first |type T| accepts a type expr and
evaluates to a meta-object, binding it to |t|. Now there are two possible interpretations for the |is| test:

  1. |is| expects a type expr, so |type t| is evidently a type expr.

No, |type t| is a way to force a type expression t to be parsed as a
value expression -- that's all.

So this is the reverse of the previous usage of the |type| operator, i.e. this |type t| accepts a meta-object and evaluates to type expr. If
this interpretation is correct, this is extremely confusing.

Not correct. Sorry for adding to the confusion (temporarily) -- that
RI session was meant to support 'is' taking a value expression on its
right, but the RI doesn't do that per the resolution of #103. Ah, but
you reply later to my subsequent messages. Ok, moving on:

  1. |is| expects either a type expr or a meta-object, but in the latter case, that meta-object must be from a |type| expr. This is a weird restriction. Furthermore, I though the |x| in |type x| had to be a
    type expr, yet in this case, |x| is a value expr (evaluating to the meta-object). Again, very confusing.

Agreed, and not what anyone wanted.

So again, how exactly is |type| supposed to work?

It's supposed to parse its operand as a type expression and evaluate
it to a value (meta-object).

Namely, I can imagine a user coming from an ES3 background to instantly see the
usefulness of structural types, yet not care at all about classes, type
parameters, or early type checking.

Possibly, although structural types either are useful for 'is like'
spot-checks (shape tests), or you end up defining names for them, and
using those names to type objects, arrays, etc. created by
initialisers, so as to get fixed property guarantees.

Classes come into the picture with 'wrap'. If you are upgrading an
existing API with structural type annotations, but you can't afford
to change all the callers to pass fixture-laden objects (this is
highly likely; and it shouldn't be necessary to change all the
callers of the API anyway), just structural type annotations will
result in type errors for all calls that pass plain old dynamic
objects and arrays. The API upgrader then has a choice: use 'like'
for spot checks, or use 'wrap' for complete coverage (in case the API
client mutates something behind the API implementations back).

GIven wrap, which creates some built-in nominal proxy type that does
(efficient, we hope, not exponentially complex) checks on all reads
and writes, you get type safety. But it can cost, even if optimized.
As your API evolves and new users come along, if they can use ES4,
you could provide nominal types for their use.

And even if you never expose nominal types in your API, the API's
factory methods could return nominal types matching the structural
type constraints in the API parameter and result annotations.

Just pointing out that even without early type checking, there are
use-cases for programmable nominal types.

Type parameters are used by the iteration protocol (not yet
implemented in the RI, but it's close). See

doku.php? id=proposals:iterators_and_generators

This protocol underpins the for-in constructs.

Nit: your definition for (c) is not using "compatible with structural type" the way we define the type compatibility relation (written "~:" instead of "<:", see doku.php? id=spec:type_relations).

BTW, I was mentioning the issue of the spec namespace being non-public because I couldn't access this page.

Sigh. I knew there was something not ancient and based on AS3
mislocated there. Sorry again, we'll get that split out somehow.

Hmm, I can see why it kind of makes sense for |is|, |cast|, and |wrap| all to require type exprs, considering they are all type operators.
But since |is| is a purely runtime check (pending Graydon's reply), it
makes no sense for it to not accept value exprs.

The tension between is/cast/wrap and is/instanceof is painful, isn't it?

So far I see a couple solutions:

  1. Let |is| work on value exprs, but leave |instanceof| alone. Then
    the difference between the two resolves to whether the operand has a prototype chain. That is, the only inputs that both will work on are classes. It's still a gotcha, but a minor one.

Noted (more below).

  1. Unify |is| and |instanceof| into a single |instanceof| operator.
    I'm still not sure what you mean by guarantees, so at the moment I
    don't see an issue other than |is| currently requiring a type expr.

Here's what I mean:

function F(){} F.prototype = new Date o = new F assert(o instanceof F) assert(!(o is F))

No mutation of the prototype property of F required (although that is
significant in my view too). It makes no sense for (o is F) => true

given that F is not a type. It's a function, and if you want function
instances to stand for types, you need fixed type names for them or
you lose decidability. But function definitions per ES3 can be
replaced by new function definitions.

This by itself argues that instanceof and 'is' should not be combined.

I also just learned from your post that AS3 already has an |is| operator that
works similarly, so that may be another barrier to this solution. | instanceof| composed with |like| also sounds awkward, e.g. |instanceof like
type {a: int}|, unless it's the other way around, e.g. |instanceof type like
{a: int}|.

(More evidence of Adobe willingness to change ES4 from AS3, in case
anyone needed it; but I'm still sympathetic to the argument that 'is'
in ES4 should accept a value expression on its right, as 'is' in AS3
does.)

  1. Let |is| work on value exprs, and upgrade |instanceof| to work on everything |is| works on. Again, not sure what you mean be guarantees.

Let's split this into its two parts:

3a) Let 'is' work on value exprs -- hold this thought. 3b) Upgrade instanceof to do what it does in ES3, but instead of
throwing a TypeError if its right operand has no [[HasInstance]]
internal method, evaluate 'is' on its operands.

This solution doesn't require the removal of |is| and so is more compatible with AS3. On the other hand, the fewer keywords, the
better.

I don't think we should combine instanceof and 'is', based on the
example I gave above.

  1. As you say (I think), |is| may be too overloaded. That it works
    both classes/interfaces and structural types even though the two are very distinct tests is a cause for concern. Yet it is convenient to group them into one operator.

No, I don't agree (and I don't think I said anything like this). 'is'
is <: the subtype relation, which is well-defined for all types.
Types are not unrelated just because some use name for equivalence
and identity, while others use structure. You can have a nominal type
be a subtype of a structural type, and this is important -- of course
you can't forge a subtype of a nominal type using a structural type,
or even by nominal inheritance of the would-be superclass is final.

You may know that Modula 3 modeled nominal types by branding
structural types. See

doku.php? id=discussion:classes_as_structural_types_with_branding

As noted, this doesn't seem like the right way to model nominal types
for several reasons. But it sometimes helps people who missed Modula
3 to see the connections between nominal and structural types.

On the other hand, it's a bit of a mash-up, in my view, to make
instanceof grow 'is' hair. The right operands are "very distinct"
between those operators today. See what I mean?

In addition to all that, if |is| works on value exprs, there's
still the confusion I've previously pointed out:

// |b| is a class, |a| is instance of |b| a is type b a is b

That's less confusing than convenient, I think. But it is a bit of
redundancy that should raise a flag. Part of the thinking in
resolving #103 in favor of type expression on right of 'is' was to
future-proof against a world where type and value expressions are
combined somehow. I don't believe that world will come to pass,
though. If it should, sooner is better. Restricting 'is' as #103-as- resolved did isn't making anyone too happy right now :-/.

Maybe we can just explain to the user that |type| is only required for structural types to avoid syntactical ambiguity (in the same vein as block {} vs. object literal {})?

Yeah, that's why I wrote "less confusing than convenient". It's not
that big of a deal compared to other issues here, IMHO.

# Brendan Eich (18 years ago)

On Nov 11, 2007, at 4:26 PM, Brendan Eich wrote:

Part of the thinking in resolving #103 in favor of type expression on right of 'is' was to future-proof against a world where type and value expressions are combined somehow. I don't believe that world will come to pass, though. If it should, sooner is better. Restricting 'is' as #103-as- resolved did isn't making anyone too happy right now :-/.

Of course, Lars cut the Gordian knot in email just a bit later today:

type T var v

x is T // if T is type then the compiler knows that, // otherwise next case applies x is v // reflect::typeOf(x).isSubtypeOf(v) // fails if v not a Type object x is { ... } // interpret as record type x is [ ... ] // interpret as array type x is ( ... ) // interpret as union type x is function ... // interpret as function type x is null // interpret as null type x is undefined // interpret as undefined type x is like T // T must be a type expression anyhow

This allows arbitrary value expressions on the right of 'is', just so
long as they don't look like type expressions in their leftmost
token. Everyone wins. Yay?

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

On Nov 11, 2007, at 3:09 PM, Yuh-Ruey Chen wrote:

Sorry, I'm still not getting it. The upgraded |instanceof| will behave exactly the same for the inputs non-upgraded |instanceof| works on. There are new guarantees, but they in no way affect the original ones.

They're stronger than the original ones, and different in kind too.
Seems like apples and oranges mixed under one operator. Not obviously
necessary given 'is', but here's a thought: instanceof does its
prototype chain walk on the left operand, looking for an object equal
to the value of the right operand's 'prototype' property, iff the
right operand has [[HasInstance]] as function objects do in ES3.
Otherwise it does a dynamic form of 'is', and for syntactic
disambiguation, you may have to use unary 'type' on the right operand.

Yes, the upgraded |instanceof| would have to perform two different types of checks. But while the value of |instanceof| can change for function operands, its value cannot change for type operands, just like |is|. The two different type checks are distinct, and the new |is|-like type check in no way affects the former prototype-chain type check, considering that the only overlap between the two is type checking against classes and the two type checks always agree on classes.

There's no way to change the prototype chain of an object instantiated from a class, right? |obj instanceof klass| is practically
equivalent to |obj is klass|. Am I missing something?

No, classes are not really the issue. The key difference is that
instanceof results can change over time for a given object on the
left and plain old function on the right, while 'is' results cannot
vary over the lifetimes of the same operands.

If |is| is a purely runtime check, does this really matter? Can you give me a use case involving a runtime type check that requires that the type check always be the same for a pair of operands?

  1. |is| expects either a type expr or a meta-object, but in the latter case, that meta-object must be from a |type| expr. This is a weird restriction. Furthermore, I though the |x| in |type x| had to be a
    type expr, yet in this case, |x| is a value expr (evaluating to the meta-object). Again, very confusing.

Agreed, and not what anyone wanted.

Ok, thanks for the clarification. However, that still doesn't answer this question: if the |x| in |type x| has to be a type expr, then how did |type x| in the example work? |x| in that case was a value expr (evaluating to a meta-object), not a type expr.

Namely, I can imagine a user coming from an ES3 background to instantly see the
usefulness of structural types, yet not care at all about classes, type
parameters, or early type checking.

Possibly, although structural types either are useful for 'is like'
spot-checks (shape tests), or you end up defining names for them, and
using those names to type objects, arrays, etc. created by
initialisers, so as to get fixed property guarantees.

Classes come into the picture with 'wrap'. If you are upgrading an
existing API with structural type annotations, but you can't afford
to change all the callers to pass fixture-laden objects (this is
highly likely; and it shouldn't be necessary to change all the
callers of the API anyway), just structural type annotations will
result in type errors for all calls that pass plain old dynamic
objects and arrays. The API upgrader then has a choice: use 'like'
for spot checks, or use 'wrap' for complete coverage (in case the API
client mutates something behind the API implementations back).

GIven wrap, which creates some built-in nominal proxy type that does
(efficient, we hope, not exponentially complex) checks on all reads
and writes, you get type safety. But it can cost, even if optimized.
As your API evolves and new users come along, if they can use ES4,
you could provide nominal types for their use.

And even if you never expose nominal types in your API, the API's
factory methods could return nominal types matching the structural
type constraints in the API parameter and result annotations.

Just pointing out that even without early type checking, there are
use-cases for programmable nominal types.

Type parameters are used by the iteration protocol (not yet
implemented in the RI, but it's close). See

doku.php? id=proposals:iterators_and_generators

This protocol underpins the for-in constructs.

I wasn't disputing the usefulness of nominal types, but thanks for the info anyway. Maintainers of large and complex ES3 libraries should definitely consider migrating to nominal types and learn the whole type system thoroughly. I was just pointing out that it would be nice to be able to use some aspects of ES4 without having to understand everything about it. Makes it more attractive to learn and more newbie-friendly, and as we all know, the web is full of them.

Hmm, I can see why it kind of makes sense for |is|, |cast|, and |wrap| all to require type exprs, considering they are all type operators.
But since |is| is a purely runtime check (pending Graydon's reply), it
makes no sense for it to not accept value exprs.

The tension between is/cast/wrap and is/instanceof is painful, isn't it?

Indeed. And it's all so subjective too...

So far I see a couple solutions:

  1. Let |is| work on value exprs, but leave |instanceof| alone. Then
    the difference between the two resolves to whether the operand has a prototype chain. That is, the only inputs that both will work on are classes. It's still a gotcha, but a minor one.

Noted (more below).

  1. Unify |is| and |instanceof| into a single |instanceof| operator.
    I'm still not sure what you mean by guarantees, so at the moment I
    don't see an issue other than |is| currently requiring a type expr.

Here's what I mean:

function F(){} F.prototype = new Date o = new F assert(o instanceof F) assert(!(o is F))

No mutation of the prototype property of F required (although that is
significant in my view too). It makes no sense for (o is F) => true
given that F is not a type. It's a function, and if you want function
instances to stand for types, you need fixed type names for them or
you lose decidability. But function definitions per ES3 can be
replaced by new function definitions.

This by itself argues that instanceof and 'is' should not be combined.

As I said before, is there any value in making sure that the value of |x is y| never changes for a given pair x and y? I can understand the importance of that guarantee for type annotations, but since |is| is a late type check, I don't see the value.

FYI, I'm not really in favor of merging to the two operators. I do recognize that there is a fundamental difference between the |instanceof| test and the |is| test. But I could also say that there is a fundamental difference between the |is| test and the |is like| test. On the other hand, unless structural types are mutable, |is| and |is like| are similar in that the values of both |is| and |is like| are for a given pair of operands can never change. And structural types are immutable, right?

Hmm, this makes me wonder if we could add an unary |instanceof| operator that's analogous to the |like| operator. Just going to write out my thoughts as they come... Consider |type T = (like some_structural_type, instanceof some_constructor}|. However, that would make the |o is T| test - and more importantly, type annotations involving T - unreliable, since its value can change for the same operands. So I guess that's a no go (just ignore this paragraph).

I also just learned from your post that AS3 already has an |is| operator that
works similarly, so that may be another barrier to this solution. | instanceof| composed with |like| also sounds awkward, e.g. |instanceof like
type {a: int}|, unless it's the other way around, e.g. |instanceof type like
{a: int}|.

(More evidence of Adobe willingness to change ES4 from AS3, in case
anyone needed it; but I'm still sympathetic to the argument that 'is'
in ES4 should accept a value expression on its right, as 'is' in AS3
does.)

  1. Let |is| work on value exprs, and upgrade |instanceof| to work on everything |is| works on. Again, not sure what you mean be guarantees.

Let's split this into its two parts:

3a) Let 'is' work on value exprs -- hold this thought. 3b) Upgrade instanceof to do what it does in ES3, but instead of
throwing a TypeError if its right operand has no [[HasInstance]]
internal method, evaluate 'is' on its operands.

This solution doesn't require the removal of |is| and so is more compatible with AS3. On the other hand, the fewer keywords, the
better.

I don't think we should combine instanceof and 'is', based on the
example I gave above.

  1. As you say (I think), |is| may be too overloaded. That it works
    both classes/interfaces and structural types even though the two are very distinct tests is a cause for concern. Yet it is convenient to group them into one operator.

No, I don't agree (and I don't think I said anything like this). 'is'
is <: the subtype relation, which is well-defined for all types.
Types are not unrelated just because some use name for equivalence
and identity, while others use structure. You can have a nominal type
be a subtype of a structural type, and this is important -- of course
you can't forge a subtype of a nominal type using a structural type,
or even by nominal inheritance of the would-be superclass is final.

You may know that Modula 3 modeled nominal types by branding
structural types. See

doku.php? id=discussion:classes_as_structural_types_with_branding

As noted, this doesn't seem like the right way to model nominal types
for several reasons. But it sometimes helps people who missed Modula
3 to see the connections between nominal and structural types.

Yeah, misunderstood you. I can see now that record types are like undeclared interfaces. However, as I noted before, the |like| test for structural types is still a different beast.

In addition to all that, if |is| works on value exprs, there's
still the confusion I've previously pointed out:

// |b| is a class, |a| is instance of |b| a is type b a is b

That's less confusing than convenient, I think. But it is a bit of
redundancy that should raise a flag. Part of the thinking in
resolving #103 in favor of type expression on right of 'is' was to
future-proof against a world where type and value expressions are
combined somehow. I don't believe that world will come to pass,
though. If it should, sooner is better. Restricting 'is' as #103-as- resolved did isn't making anyone too happy right now :-/.

Maybe we can just explain to the user that |type| is only required for structural types to avoid syntactical ambiguity (in the same vein as block {} vs. object literal {})?

Yeah, that's why I wrote "less confusing than convenient". It's not
that big of a deal compared to other issues here, IMHO.

/be

A thought: If the |type| operator can accept a value expr that resolves to a meta-object and returns that meta-object, then |type| can be chained, e.g. |type type type T| is equivalent to |type T|. In this way, a user that's unsure if a particular type expression is ambigious can just prefix it with |type| without worries.

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 11, 2007, at 10:41 PM, Yuh-Ruey Chen wrote:

The key difference is that instanceof results can change over time for a given object on the left and plain old function on the right, while 'is' results cannot vary over the lifetimes of the same operands.

If |is| is a purely runtime check, does this really matter? Can you
give me a use case involving a runtime type check that requires that the
type check always be the same for a pair of operands?

Your poor-man's type parameters example will do. The 'is' operator is
how you write an expression testing subtype, and the : type
annotation colon enforces subtype relation for all writes to the
annotated slot. So 'is' and annotations are closely related (ignore
the hardcoded conversions within AnyString, AnyNumber, and AnyBoolean
respectively).

Ok, thanks for the clarification. However, that still doesn't answer this question: if the |x| in |type x| has to be a type expr, then how did |type x| in the example work? |x| in that case was a value expr (evaluating to a meta-object), not a type expr.

Maybe that was just a mistake :-).

The tension between is/cast/wrap and is/instanceof is painful,
isn't it?

Indeed. And it's all so subjective too...

See latest mail from me, Lars's Gordian knot slicing. It makes 'is'
DWIM. 'cast' need not accept value expressions since it is useful
only for the type checker (optional strict mode), and for
documentation purposes. 'wrap' should do what 'like' does (require a
type expression, currently).

As I said before, is there any value in making sure that the value
of |x is y| never changes for a given pair x and y? I can understand the importance of that guarantee for type annotations, but since |is| is a late type check, I don't see the value.

Even if (as we believe so far) 'is' should not be analyzed by strict
mode, you want the parallel between type annotations and 'is' to hold
up over the lifetime of the slot in the case of a type annotation,
and the operands in the case of 'is'. Well, I do, at any rate. Both
'let x:T' and 'x is T' invoke the subtype relation. And the prototype
relation is not the subtype relation.

FYI, I'm not really in favor of merging to the two operators. I do recognize that there is a fundamental difference between the |instanceof| test and the |is| test. But I could also say that
there is a fundamental difference between the |is| test and the |is like| test.

Remember, nothing special goes on with 'is like'. Imagine explicit
parenthese (not allowed because they mean union type, but pretend):

x is T x is (like T)

We know this must be the case since you could have defined

type T = like U

before the first line (x is T), and the result should be the same as
if you expanded the typename:

x is like U

On the other hand, unless structural types are mutable, |is| and |is like| are similar in that the values of both |is| and |is like| are
for a given pair of operands can never change. And structural types are immutable, right?

Right. But don't believe 'is like' is a compound-keyword type
operator -- it's two operators, the right one a unary prefix type op
(type constructor really).

Yeah, misunderstood you. I can see now that record types are like undeclared interfaces.

Not sure if you meant anonymous interfaces, but interfaces can have
only methods, which can be overridden along the inheritance chain in
the nominal type system. Structural types on the other hand describe
records with fields -- not methods, although a field's type could be
a function structural type.

Bottom line: interfaces != structural object types.

However, as I noted before, the |like| test for structural types is still a different beast.

There's a possible generalization of 'like' to accept any type on its
right (conversion is not an issue: bugs.ecmascript.org/ticket 258). Remember, no non-compositional special cases or ad-hoc patches,
if we can avoid 'em.

A thought: If the |type| operator can accept a value expr that
resolves to a meta-object and returns that meta-object, then |type| can be chained, e.g. |type type type T| is equivalent to |type T|. In this
way, a user that's unsure if a particular type expression is ambigious can just prefix it with |type| without worries.

But the parser (not optional strict mode, the fundamental grammar)
will reject a non-type expression (if we want it to). This is a case
of load/edit/reload at worst.

But hey, I'm jazzed by Lars's "have our cake and eat it" proposal for
'is'. For instanceof, we could do what you and I seem to be talking
about: extend it to do what 'is' does, with the gravy that its right
operand is always a value expression. So 'instanceof' could be used
to handle all kinds of instantiation, ES3 mutable-prototype function
constructors and ES4 types. Comments?

# YR Chen (18 years ago)

Responding to 2 emails here...

On Nov 12, 2007 12:35 AM, Brendan Eich <brendan at mozilla.org> wrote:

On Nov 11, 2007, at 4:26 PM, Brendan Eich wrote:

Part of the thinking in resolving #103 in favor of type expression on right of 'is' was to future-proof against a world where type and value expressions are combined somehow. I don't believe that world will come to pass, though. If it should, sooner is better. Restricting 'is' as #103-as- resolved did isn't making anyone too happy right now :-/.

Of course, Lars cut the Gordian knot in email just a bit later today:

type T var v

x is T // if T is type then the compiler knows that, // otherwise next case applies x is v // reflect::typeOf(x).isSubtypeOf(v) // fails if v not a Type object x is { ... } // interpret as record type x is [ ... ] // interpret as array type x is ( ... ) // interpret as union type x is function ... // interpret as function type x is null // interpret as null type x is undefined // interpret as undefined type x is like T // T must be a type expression anyhow

This allows arbitrary value expressions on the right of 'is', just so long as they don't look like type expressions in their leftmost token. Everyone wins. Yay?

/be

Definitely like it. I wonder how convoluted the grammar change was - did it require a new value_expr_no_conflicts_with_type_expr production and all related productions/rules?

I don't want to rain on the parade, but I have some worries on how it would impact an upgraded |instanceof|. More below.

On Nov 12, 2007 1:04 AM, Brendan Eich <brendan at mozilla.org> wrote:

On Nov 11, 2007, at 10:41 PM, Yuh-Ruey Chen wrote:

The key difference is that instanceof results can change over time for a given object on the left and plain old function on the right, while 'is' results cannot vary over the lifetimes of the same operands.

If |is| is a purely runtime check, does this really matter? Can you give me a use case involving a runtime type check that requires that the type check always be the same for a pair of operands?

Your poor-man's type parameters example will do. The 'is' operator is how you write an expression testing subtype, and the : type annotation colon enforces subtype relation for all writes to the annotated slot. So 'is' and annotations are closely related (ignore the hardcoded conversions within AnyString, AnyNumber, and AnyBoolean respectively).

I see - I was thinking that it was a technical limitation - but apparently not. You've convinced me that pairing the semantics of |is| and type notations is just as important, if not more, as pairing the syntax of |is| and |instanceof|, so I'll drop the attempt to unify |is| and |instanceof|.

Ok, thanks for the clarification. However, that still doesn't answer this question: if the |x| in |type x| has to be a type expr, then how did |type x| in the example work? |x| in that case was a value expr (evaluating to a meta-object), not a type expr.

Maybe that was just a mistake :-).

Doh!

FYI, I'm not really in favor of merging to the two operators. I do recognize that there is a fundamental difference between the |instanceof| test and the |is| test. But I could also say that there is a fundamental difference between the |is| test and the |is like| test.

Remember, nothing special goes on with 'is like'. Imagine explicit parenthese (not allowed because they mean union type, but pretend):

x is T x is (like T)

We know this must be the case since you could have defined

type T = like U

before the first line (x is T), and the result should be the same as if you expanded the typename:

x is like U

I'm aware that |like| is an unary type operator that can be used outside of |is| exprs. It's just more convenient to write |is like| instead of "|is| composed with |like|" :)

Yeah, misunderstood you. I can see now that record types are like undeclared interfaces.

Not sure if you meant anonymous interfaces, but interfaces can have only methods, which can be overridden along the inheritance chain in the nominal type system. Structural types on the other hand describe records with fields -- not methods, although a field's type could be a function structural type.

Bottom line: interfaces != structural object types.

Yeah, I meant "anonymous interfaces". I know they're not the same as interfaces, but they're similar in that they place a set of restrictions or guarantees on a class or object that "implements" them.

However, as I noted before, the |like| test for structural types is still a different beast.

There's a possible generalization of 'like' to accept any type on its right (conversion is not an issue: bugs.ecmascript.org/ticket 258). Remember, no non-compositional special cases or ad-hoc patches, if we can avoid 'em.

I think that would be a good thing. Another thought: if |x is v|, where |v| is a meta-object, works, then one would think that it should also work for |like|, e.g.

var x = int 10 is x; // ok 10 is like x; // ?

But if that last statement is allowed, then so is |var y: like x|, which is undecidable (if that's the correct term), so evidently |like| shouldn't work with value exprs.

Might want to mention this in any clarification you put into the spec, even if it's strictly disallowed in the formal grammar - that even though |is| allows a value expr on the right side, it cannot be nested within a type expr. That means given |var x|, none of |10 is like x|, |10 is (x)|, |10 is {p: x}|, etc. are syntactically allowed.

A thought: If the |type| operator can accept a value expr that resolves to a meta-object and returns that meta-object, then |type| can be chained, e.g. |type type type T| is equivalent to |type T|. In this way, a user that's unsure if a particular type expression is ambigious can just prefix it with |type| without worries.

But the parser (not optional strict mode, the fundamental grammar) will reject a non-type expression (if we want it to). This is a case of load/edit/reload at worst.

Okay, this proposal stemmed from the error in your previous example, in which you had |type x| where |x| was a value expr resolving to a meta-object. I was thinking that if |type| could already recognize both value exprs evaluating to meta-objects and type exprs, then it would be trivial to implement this. Whether it is needed or not...see below.

But hey, I'm jazzed by Lars's "have our cake and eat it" proposal for 'is'. For instanceof, we could do what you and I seem to be talking about: extend it to do what 'is' does, with the gravy that its right operand is always a value expression. So 'instanceof' could be used to handle all kinds of instantiation, ES3 mutable-prototype function constructors and ES4 types. Comments?

/be

There are couple potential problems with upgrading |instanceof| to match the syntax of the revised |is|:

  1. Function expr syntax ambiguity. Consider:

a) x is function(p: int): int // ok b) x is function(p: int): int {} // syntax error c) x instanceof function(p: int): int // ok? d) x instanceof function(p: int): int {} // syntax error?

Will it try to parse the |function ...| in (c)/(d) as a function expr or a function type? (d), ignoring the type annotations, is currently legal in ES3 and is pretty much guaranteed to resolve to false. In the upgraded |instanceof| however, if |function ...| is interpreted as a type, it would be a syntax error. On the other hand, if |function ...| is interpreted as a function expr, (c) would be a syntax error and it |instanceof| would no longer match |is|. So treating |function ...| as a type will be backwards compatible, but the compat issue is so minor (who the heck would do (d)?) that I wouldn't mind it.

  1. Object literal syntax ambiguity. If it is possible to simulate a constructor with an object literal, namely creating an object with [[HasInstance]], we'll have a similar situation to (1). I'm not sure if it's possible, so I'll make up some syntax in the following example to indicate what I mean:

x instanceof {meta::construct: function() {}}

That object literal is supposed to create a constructor-like object, but like I said, I'm not sure if it's really possible and of the exact syntax required. Anyway, that |{...}| is ambiguous in that it can either be treated as a record structural type or an object literal. In ES3, there's no way to create a constructor from an object literal (AFAIK), so the above syntax (ignoring the namespace) would be a runtime error. Thus, there is no backwards compat issue. If it were allowed in ES3, as before, it is guaranteed to resolve to false. So the |{...}| should be treated as a record structural type.

  1. Parenthesis ambiguity. This is the most troublesome one. Consider:

a) x is (int) // ok b) x is (some_constructor) // syntax error c) x is ({p: int}) // ok d) x instanceof (int) // ok e) x instanceof (some_constructor) // syntax error? f) x instanceof ({p: int}) // how should this be treated?

(e) is currently allowed in ES3, and unlike the the function expr ambiguity, |(some_constructor)| is meaningful. That means it must be allowed in ES4 for backwards compatibility. However, to match |is|, |(...)| must be treated as a union type. And whether |(...)| is treated as a union type or a value parens expr really matters as shown in (f). I'm stumped.

If we really want to upgrade |instanceof| yet keep it coherent with |is| in both semantics and syntax, we may have to visit the |type| operator again, but that should be a last resort.

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 12, 2007, at 1:40 PM, YR Chen wrote:

Definitely like it. I wonder how convoluted the grammar change was

  • did it require a new value_expr_no_conflicts_with_type_expr
    production and all related productions/rules?

Not sure, but top-down parsers have it easy, discriminating on the
leftmost token and if none of the short list of structural type
tokens match, falling into a value-expression sub-parser invocation.

As I noted elsewhere, we have such a grammar (not quite the same,
consider '(' as the leftmost token of the new operand) for the new
operator (see the overview, "Record and array types" section).

Yeah, I meant "anonymous interfaces". I know they're not the same
as interfaces, but they're similar in that they place a set of
restrictions or guarantees on a class or object that "implements"
them.

Just FYI, we tried for self-types (see Kim Bruce's work) to make
structural types with "methods" stronger, but deferred. See

proposals:self_type

I think that would be a good thing. Another thought: if |x is v|,
where |v| is a meta-object, works, then one would think that it
should also work for |like|, e.g.

var x = int 10 is x; // ok 10 is like x; // ?

But if that last statement is allowed, then so is |var y: like x|,
which is undecidable (if that's the correct term), so evidently | like| shouldn't work with value exprs.

This is a hot topic. We could indeed allow all sorts of type
expressions, and define evaluation rules so that (I hope) nothing
diverges and we don't need to set a watchdog timer on strict mode's
type checker. The static guarantees go down because strict mode will
punt anything it can't figure out to runtime, treating the compile- time type as *. It seems wiser at the moment to restrict type
annotations and remain future proof, but make 'is' friendlier as you
and I have been discussing.

Might want to mention this in any clarification you put into the
spec, even if it's strictly disallowed in the formal grammar - that
even though |is| allows a value expr on the right side, it cannot
be nested within a type expr. That means given |var x|, none of |10
is like x|, |10 is (x)|, |10 is {p: x}|, etc. are syntactically
allowed.

I think we're now inclined to allow those but insist on a type at
runtime. But this is something to discuss more.

There are couple potential problems with upgrading |instanceof| to
match the syntax of the revised |is|:

  1. Function expr syntax ambiguity. Consider:

a) x is function(p: int): int // ok b) x is function(p: int): int {} // syntax error c) x instanceof function(p: int): int // ok? d) x instanceof function(p: int): int {} // syntax error?

Oh, I see -- on second thought I meant nothing like allowing (d) --
sorry. instanceof only takes a value expression on its right, but if
that evaluates to a type meta-object, it does something sane and
"instance-of"ish.

  1. Object literal syntax ambiguity. If it is possible to simulate a
    constructor with an object literal, namely creating an object with
    [[HasInstance]], we'll have a similar situation to (1). I'm not
    sure if it's possible, so I'll make up some syntax in the following
    example to indicate what I mean:

x instanceof {meta::construct: function() {}}

That object literal is supposed to create a constructor-like
object, but like I said, I'm not sure if it's really possible and
of the exact syntax required.

Not possible, so if we want to allow {p: t, q: u} on the right of
instanceof, we have the choice to treat that as a type expression
(likewise for [a, b] and (c, d)). Again the new operator can handle
array and object types similarly, because there's no way for you to
write a constructible value initialiser (object or, needless to say,
array initialiser). If we do not fear closing the door to future
extensions that enable constructible or has-instance-able
initialisers, then I think we're on solid ground. And I see no point
in adding ways to make such things, given the alternative meanings
competing for the same syntax here (value vs. type expressions with
instanceof and new, I mean).

Anyway, that |{...}| is ambiguous in that it can either be treated
as a record structural type or an object literal. In ES3, there's
no way to create a constructor from an object literal (AFAIK), so
the above syntax (ignoring the namespace) would be a runtime error.
Thus, there is no backwards compat issue. If it were allowed in
ES3, as before, it is guaranteed to resolve to false. So the | {...}| should be treated as a record structural type.

  1. Parenthesis ambiguity. This is the most troublesome one. Consider:

a) x is (int) // ok b) x is (some_constructor) // syntax error c) x is ({p: int}) // ok d) x instanceof (int) // ok e) x instanceof (some_constructor) // syntax error? f) x instanceof ({p: int}) // how should this be treated?

Yes, and parenthesized expression may follow operator new, so this is
a hard limit.

If we really want to upgrade |instanceof| yet keep it coherent with
|is| in both semantics and syntax, we may have to visit the |type|
operator again, but that should be a last resort.

That unary type operator is still around in the spec space, awaiting
hookup in the RI, at least as far as I can recall. But with the
unification (partial, but practical) of type and (else) value
expressions for 'is", or vice versa for instanceof/new, I agree we
should avoid reaching for unary 'type'.

# Brendan Eich (18 years ago)

On Nov 12, 2007, at 3:58 PM, Brendan Eich wrote:

There are couple potential problems with upgrading |instanceof| to match the syntax of the revised |is|:

  1. Function expr syntax ambiguity. Consider:

a) x is function(p: int): int // ok b) x is function(p: int): int {} // syntax error c) x instanceof function(p: int): int // ok? d) x instanceof function(p: int): int {} // syntax error?

Oh, I see -- on second thought I meant nothing like allowing (d) --

Er, I meant (c), if you remove type annotations from the function
param and result. In ES3 today, you can write

js> ({}) instanceof function (){}

false

It's silly, of course, because the function expression is captured by
the right operand and could not have been constructed via operator
new to get the left operand. But it's valid ES3 syntax, so we can't
switch instanceof's right operand to favor a function structural type.

# P T Withington (18 years ago)

On 2007-11-12, at 02:04 EST, Brendan Eich wrote:

                                            Imagine explicit

parenthese (not allowed because they mean union type, but pretend)

I was just thinking how clever it was that parentheses meant both
union type and expression grouping, because a union type of only one
type is that type, so you could use parentheses here?

x is (like T)

What am I missing?

# Brendan Eich (18 years ago)

On Nov 13, 2007, at 1:08 PM, P T Withington wrote:

On 2007-11-12, at 02:04 EST, Brendan Eich wrote:

                                            Imagine explicit

parenthese (not allowed because they mean union type, but pretend)

I was just thinking how clever it was that parentheses meant both union type and expression grouping, because a union type of only one type is that type, so you could use parentheses here?

x is (like T)

What am I missing?

Heh, I was not paying attention -- you're quite right, thanks.

Separately, there seems to be some concern that users would want to
parenthesize a value expression on the right of 'is'. Not sure why,
but the exchange in

bugs.ecmascript.org/ticket/300

may provide that use-case. Or discussion here, which is a better
venue for threaded discussions than simple-minded Trac.

Thanks again,

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

I think that would be a good thing. Another thought: if |x is v|,
where |v| is a meta-object, works, then one would think that it
should also work for |like|, e.g.

var x = int 10 is x; // ok 10 is like x; // ?

But if that last statement is allowed, then so is |var y: like x|,
which is undecidable (if that's the correct term), so evidently | like| shouldn't work with value exprs.

This is a hot topic. We could indeed allow all sorts of type
expressions, and define evaluation rules so that (I hope) nothing
diverges and we don't need to set a watchdog timer on strict mode's
type checker. The static guarantees go down because strict mode will
punt anything it can't figure out to runtime, treating the compile- time type as *. It seems wiser at the moment to restrict type
annotations and remain future proof, but make 'is' friendlier as you
and I have been discussing.

I agree.

Might want to mention this in any clarification you put into the
spec, even if it's strictly disallowed in the formal grammar - that
even though |is| allows a value expr on the right side, it cannot
be nested within a type expr. That means given |var x|, none of |10
is like x|, |10 is (x)|, |10 is {p: x}|, etc. are syntactically
allowed.

I think we're now inclined to allow those but insist on a type at
runtime. But this is something to discuss more.

I don't see how that's workable. I mean, technically it is, since |is| is a runtime check. But it creates another "incompatibility" between type annotations and |is|, and you seem pretty adamant to keep the two as coherent as possible. Just consider:

T = cond? int : double; x is {p: T}; // ok var y : like {p: T}; // early type error

There are couple potential problems with upgrading |instanceof| to
match the syntax of the revised |is|:

  1. Function expr syntax ambiguity. Consider:

a) x is function(p: int): int // ok b) x is function(p: int): int {} // syntax error c) x instanceof function(p: int): int // ok? d) x instanceof function(p: int): int {} // syntax error?

Oh, I see -- on second thought I meant nothing like allowing (d) --
sorry. instanceof only takes a value expression on its right, but if
that evaluates to a type meta-object, it does something sane and
"instance-of"ish.

So I take that |x instanceof {p: int}| won't work, and we'd have to use |T = type {p: int}; x instanceof T| instead?

From your other email: Er, I meant (c), if you remove type annotations from the function
param and result. In ES3 today, you can write

js> ({}) instanceof function (){} false

It's silly, of course, because the function expression is captured by
the right operand and could not have been constructed via operator
new to get the left operand. But it's valid ES3 syntax, so we can't
switch instanceof's right operand to favor a function structural type.

I truly doubt there is any code out there with (d). It's a backwards incompatibility I'd be willing to break. If there is code out there with this, well, the new syntax error should tell them that they're doing something really stupid.

  1. Parenthesis ambiguity. This is the most troublesome one. Consider:

a) x is (int) // ok b) x is (some_constructor) // syntax error c) x is ({p: int}) // ok d) x instanceof (int) // ok e) x instanceof (some_constructor) // syntax error? f) x instanceof ({p: int}) // how should this be treated?

Yes, and parenthesized expression may follow operator new, so this is
a hard limit.

Plenty of discussion going on in ticket 300 concerning this. Ugh, this semi-merging of value and type exprs is getting awkward. We're ending up with just as many gotchas as we had before at this rate.

Alright, overview time again. Our current goals are:

  1. Make |is| less restrictive and allow it accept (some) value exprs.
  2. Keep |is| and type annotations coherent.
  3. Keep |is| and |instanceof| (somewhat) coherent.
  4. Keep all the type operators coherent (to a certain extent).
  5. Try not to introduce too many exceptions to the rule a.k.a. gotchas.

Whatever we do to advance one goal, another goal becomes more compromised. Maybe we can place priorities on these goals? If we can abandon one or two of these in favor of the other goals, this job would be much simpler. Need to think on this some more...

-Yuh-Ruey Chen

# Brendan Eich (18 years ago)

On Nov 13, 2007, at 10:40 PM, Yuh-Ruey Chen wrote:

I don't see how that's workable. I mean, technically it is, since |is| is a runtime check. But it creates another "incompatibility" between type annotations and |is|, and you seem pretty adamant to keep the two as coherent as possible.

You're right, there are problems with unifying type and value
expressions, giving type expressions priority. There may even be
problems with just type expression syntax as proposed, see http:// bugs.ecmascript.org/ticket/309.

d) x instanceof function(p: int): int {} // syntax error?

Oh, I see -- on second thought I meant nothing like allowing (d) -- sorry. instanceof only takes a value expression on its right, but if that evaluates to a type meta-object, it does something sane and "instance-of"ish.

So I take that |x instanceof {p: int}| won't work, and we'd have to
use |T = type {p: int}; x instanceof T| instead?

No, the restriction on instanceof comes from ES3, which introduced
it, along with object initialisers. So x instanceof {p:int} (given a
binding for 'int' of course) is perfectly valid ES3 and ES4. It
throws an error in ES3 implementations that do not reserve
'int' (e.g. SpiderMonkey):

js> int = function (){}

function () { } js> x = 42

42 js> x instanceof {p:int}

typein:3: TypeError: invalid 'instanceof' operand ({p:(function () {})})

I truly doubt there is any code out there with (d). It's a backwards incompatibility I'd be willing to break. If there is code out there
with this, well, the new syntax error should tell them that they're doing something really stupid.

You're no doubt right, but why mess with instanceof to take a type
expression on the right? We still have to work out type expression
grammar as it intrudes into value expressions to everyone's
satisfaction. I'd rather keep instanceof's right operand a value
expression and fry other fish.

Plenty of discussion going on in ticket 300 concerning this. Ugh, this semi-merging of value and type exprs is getting awkward. We're
ending up with just as many gotchas as we had before at this rate.

Yes, this merge attempt failed already -- it was doomed from the
start. We need to get type expressions by themselves working. I'm
pretty sure we can resolve all grammatical issues to-do with type
expressions, and in that event, allowing a runtime name instead of
insisting only on a fixed type name will be easy.

Alright, overview time again. Our current goals are:

  1. Make |is| less restrictive and allow it accept (some) value exprs.

At this point (some) means name expressions, not restricted to be
fixed type names.

  1. Keep |is| and type annotations coherent.

Check, but allowing arbitrary names in type annotations is out. Still
coherent enough. Call it compromise, if you must -- I think 'is' the
operator has to compromise because it has too many connotations, and
different use-cases from type annotations.

  1. Keep |is| and |instanceof| (somewhat) coherent.

We should define coherence more precisely:

  • 'is' takes a type expression on the right, but allows runtime name
    in addition to fixed type name.
  • instanceof takes a value expression per ES3 but if its result lacks
    a [[HasInstance]] internal method (new formalism for ES4 needed, I'm
    using ES3 meta-methods here), it checks for a meta-object result and
    does 'is' -- else it throws TypeError.
  1. Keep all the type operators coherent (to a certain extent).

Let's see:

  • 'cast' requires a fixed type expression on the right (no variable
    name)
  • 'wrap' and 'like' require fixed type expressions.
  1. Try not to introduce too many exceptions to the rule a.k.a.
    gotchas.

Yup.

Whatever we do to advance one goal, another goal becomes more compromised. Maybe we can place priorities on these goals? If we can abandon one or two of these in favor of the other goals, this job
would be much simpler. Need to think on this some more...

Let me know what you think of my interpretations and elaborations
just above. Thanks,

# Yuh-Ruey Chen (18 years ago)

Brendan Eich wrote:

So I take that |x instanceof {p: int}| won't work, and we'd have to
use |T = type {p: int}; x instanceof T| instead?

No, the restriction on instanceof comes from ES3, which introduced
it, along with object initialisers. So x instanceof {p:int} (given a
binding for 'int' of course) is perfectly valid ES3 and ES4. It
throws an error in ES3 implementations that do not reserve
'int' (e.g. SpiderMonkey):

js> int = function (){} function () { } js> x = 42 42 js> x instanceof {p:int} typein:3: TypeError: invalid 'instanceof' operand ({p:(function () {})})

That was a bad example, I meant to ask whether |instanceof| could take any type expr on the right. But now that I think of the ambiguities like th, the answer to that is probably a no.

Plenty of discussion going on in ticket 300 concerning this. Ugh, this semi-merging of value and type exprs is getting awkward. We're
ending up with just as many gotchas as we had before at this rate.

Yes, this merge attempt failed already -- it was doomed from the
start. We need to get type expressions by themselves working. I'm
pretty sure we can resolve all grammatical issues to-do with type
expressions, and in that event, allowing a runtime name instead of
insisting only on a fixed type name will be easy.

I have a proposal - see below.

Alright, overview time again. Our current goals are:

  1. Make |is| less restrictive and allow it accept (some) value exprs.

At this point (some) means name expressions, not restricted to be
fixed type names.

  1. Keep |is| and type annotations coherent.

Check, but allowing arbitrary names in type annotations is out. Still
coherent enough. Call it compromise, if you must -- I think 'is' the
operator has to compromise because it has too many connotations, and
different use-cases from type annotations.

  1. Keep |is| and |instanceof| (somewhat) coherent.

We should define coherence more precisely:

  • 'is' takes a type expression on the right, but allows runtime name
    in addition to fixed type name.

Although I was initially in favor of this, now I'm not because it's a rather noticeable exception to the rule.

  • instanceof takes a value expression per ES3 but if its result lacks
    a [[HasInstance]] internal method (new formalism for ES4 needed, I'm
    using ES3 meta-methods here), it checks for a meta-object result and
    does 'is' -- else it throws TypeError.
  1. Keep all the type operators coherent (to a certain extent).

Let's see:

  • 'cast' requires a fixed type expression on the right (no variable
    name)
  • 'wrap' and 'like' require fixed type expressions.
  1. Try not to introduce too many exceptions to the rule a.k.a.
    gotchas.

Yup.

(Quoting everything, since I'll be referring to these goals.) There's one goal I forgot to list:

  1. Syntax brevity - the shorter the syntax, the better.

Whatever we do to advance one goal, another goal becomes more compromised. Maybe we can place priorities on these goals? If we can abandon one or two of these in favor of the other goals, this job
would be much simpler. Need to think on this some more...

Let me know what you think of my interpretations and elaborations
just above. Thanks,

/be

It would be helpful if you can attach some priorities to those goals.

In the absence of "official" priorities, I've crafted a proposal. The goals I'll be trying to satisfy are everything except brevity (partially). Here's the gist of it:

If the main problem is that value exprs and type exprs are incompatible, then why not make them compatible? Make it so that the only difference between the two is that type-expr-like value exprs evaluate to meta-objects and actual type exprs require fixed type properties. With these as the only two differences and with no exceptions, I hope it will be as intuitive as possible. The additional benefits is that type exprs can be used much more freely since they will be a subset of value exprs. Since the syntax of value exprs is fixed (due to compatibility constraints), the only way to do this is to adjust the syntax of type exprs. And this is where I'll violate the syntax brevity goal, which may not be such a bad thing since it the intent of the syntax is now more clear. Now to get into details:

The only real non-syntactic issue between type exprs and value exprs is that in type exprs, the identifiers must be fixed type properties. Everything else, such structural types and function types (ignoring the identifiers), is fixed. For ex, |{p: x}| will always mean the same thing, given that x is a fixed type; it can't be mutated. The rest of the issues are syntactic ambiguities. So for each of these ambiguities that I'm aware of, I'll either prefix it with |type| or "match" the semantics between the value and type expr. Note that |type| is no longer an operator; it can only be used in certain situations as listed below (and as the type declaration/alias statement).

  1. structural record and array types: Require prefixing with |type|. e.g. |type {p: int}|.

  2. union types: Create a new union meta-object type so that "x | y", where both x and y are meta-objects, evaluates to a meta-object of this type representing the union of the types within x and y. The enclosing parentheses can be omitted, but |()| still means the bottom type. Thus, the parenthesis only purpose now is grouping. However, since |is| and |instanceof| have higher precedence than "|", the parentheses must be used for "|" in those exprs.

  3. function types: Require prefixing with |type|, e.g. |type function(a: int): void|.

  4. type parameters: With the above changes, not sure if anything needs to be changed since type exprs are now a subset of value exprs.

Furthermore, now that type exprs and value exprs are unified, |is| can now accept value exprs. For type annotations, type declarations, and every other type operator besides |is| and |instanceof| (and |new|), if any identifier would resolve to a non-fixed type property, then throw a syntax error. As for future-proofing, we just have to make sure that new type exprs are unambiguous and prefix the new syntax with |type| as necessary.

With this I should meet all goals except the brevity one, and even that goal isn't violated very much. So what does this all mean? I love examples, so I'll be liberal with them:

Given: type T1 = int type T2 = double V1 = int V2 = double

// valid (note how V1 and V2 are freely used): x is int x is type {p: int} x is like int x is like type {p: int} x is (T1 | T2) x is V1 x is (V1 | T2) x is {p: V2} x is (like V1 | type {p: V2} | string) x is () x is (((((T1))))) x is (((((V1))))) x is [like V2] x is type function(a: T1, b: type {p: V1}): V2

// invalid (|type| is no longer an operator): x is type int x is type T1 x is type V1

// although "|" has low precedence, that precedence is higher than statements and type annotations type T3 = T1 | T2 V3 = T1 | V2 var x: T1 | T2 function foo(a: T1 | T2)

// invalid (|type| prefix is required) type T5 = {p: int} type T5 = [T1] type T5 = function(): void function foo(a: {p: int})

// |instanceof| shares same syntax with |is| now: x instanceof int x instanceof like int x instanceof (like T1 | type {p: V2}) // but still works with constructors (even if meaningless) x instanceof function(){}

// valid type annotations: var x: T1 var x: type [T1] var x: like type {p: T2} var x: T1 | T2 function foo(a: T1, b: T2)

// invalid type annotations (if they contain any non-fixed type property identifiers) var x: V1 var x: T1 | T2 | V1 var x: T1 | (type {p: T2, q: type [like V1]} function foo(a: T1, b: V2)

|new| remains the same. All the other type operators have the same restrictions as type annotations (allow only fixed type properties), so don't need examples for them.

Thoughts? I hope I haven't missed anything.

-Yuh-Ruey Chen

# Yuh-Ruey Chen (18 years ago)

Yuh-Ruey Chen wrote:

If the main problem is that value exprs and type exprs are incompatible, then why not make them compatible? Make it so that the only difference between the two is that type-expr-like value exprs evaluate to meta-objects and actual type exprs require fixed type properties. With these as the only two differences and with no exceptions, I hope it will be as intuitive as possible. The additional benefits is that type exprs can be used much more freely since they will be a subset of value exprs. Since the syntax of value exprs is fixed (due to compatibility constraints), the only way to do this is to adjust the syntax of type exprs. And this is where I'll violate the syntax brevity goal, which may not be such a bad thing since it the intent of the syntax is now more clear. Now to get into details:

The only real non-syntactic issue between type exprs and value exprs is that in type exprs, the identifiers must be fixed type properties. Everything else, such structural types and function types (ignoring the identifiers), is fixed. For ex, |{p: x}| will always mean the same thing, given that x is a fixed type; it can't be mutated. The rest of the issues are syntactic ambiguities. So for each of these ambiguities that I'm aware of, I'll either prefix it with |type| or "match" the semantics between the value and type expr. Note that |type| is no longer an operator; it can only be used in certain situations as listed below (and as the type declaration/alias statement).

Also, the elimination of the separation between type exprs and value exprs in favor of focusing on fixed type properties vs. non-fixed properties should be vaguely familiar with C++ programmers. If you consider fixed type properties in type exprs as a form of C++ "const", then it's similar in that you have to make sure everything that everything that is const, only contains const exprs, and ultimately const identifiers. Actually, a more proper analogous example would be C++ templates, which work on the equivalent of ES4 fixed types (and const values). This fixed vs. non-fixed distinction is the only thing users need to be aware of - otherwise, they can mix and match type and value exprs freely. That's why I argue that this is easier to grasp (or at least more familiar) than the current type expr vs. value expr distinction.

-Yuh-Ruey Chen

# Yuh-Ruey Chen (18 years ago)

More clarifications and updates to my proposal:

Yuh-Ruey Chen wrote:

The only real non-syntactic issue between type exprs and value exprs is that in type exprs, the identifiers must be fixed type properties. Everything else, such structural types and function types (ignoring the identifiers), is fixed. For ex, |{p: x}| will always mean the same thing, given that x is a fixed type; it can't be mutated. The rest of the issues are syntactic ambiguities. So for each of these ambiguities that I'm aware of, I'll either prefix it with |type| or "match" the semantics between the value and type expr. Note that |type| is no longer an operator; it can only be used in certain situations as listed below (and as the type declaration/alias statement).

Just realized a problem. The grouping operator (parentheses) means something different for type exprs and value exprs. Type exprs can only nest type exprs within parenthesis, while value exprs can nest any value expr within them. Couple choices here:

  1. Leave this is a distinction between type expr and value expr. Downside to this is that type expr and value expr won't fully be unified (and the downside to that has been amply discussed).

  2. Eliminate the ambiguity in the grammar. Introduce |type (...)|. Downside is the verbosity and possible confusion as to when to use |type (...)| rather than |(...)| (or at least why they have to be different).

  3. Don't change the grammar and handle the ambiguity by keeping track of whether a particular expression resolves to a type and throwing early errors when we're in a type expr context and we're trying to perform an illegal operation on the type (e.g. non-type operators like +). Since types are computed early, determining whether a particular expr resolves to a type can also be computed early, so this type of processing can be done. I have some experience making a pseudo-C parser, and this is similar to keeping track of whether an expr is an lvalue. In essence, the term "type expr" is not a grammar production and is unrelated to operator precedence; it's an "aspect" of an expression (just like lvalues). Downside to this is that it complicates the parser logic.

I think (3) is the best solution. It's the most convenient for the user. It does make the learning curve slightly steeper in that now the user needs to keep in mind that type exprs cannot contain non-type exprs AND in type annotations and all type operators besides |is| and |instanceof|, type exprs must contain only fixed type prop identifiers. But I don't think it's that bad.

Also, although it's not strictly necessary to keep the "type exprs only nest type exprs" principle outside of type annotations et al., I think it makes type annotations and |is| more coherent, in that the only difference between them is the fixed type prop identifier restriction. BTW, we really need a term that represents type exprs in type annotations et al.; it's getting repetitive to say "type exprs in type annotations and all type operators besides |is| and |instanceof|" all the time...

  1. union types: Create a new union meta-object type so that "x | y", where both x and y are meta-objects, evaluates to a meta-object of this type representing the union of the types within x and y. The enclosing parentheses can be omitted, but |()| still means the bottom type. Thus, the parenthesis only purpose now is grouping. However, since |is| and |instanceof| have higher precedence than "|", the parentheses must be used for "|" in those exprs.

The way I've defined the union operator here introduces another value vs. type expr conflict. Although pretty nifty, it would allow exprs like: "x | func()" or "(b? int : double) | string", which shouldn't be allowed in type exprs considering that type exprs should only nest other type exprs. The precedence of "|" also poses an issue since it's completely different from that of type primaries. The operands of "|" can not only be type primaries but also any value expr with higher predence than "|". This violates the principle that type exprs can only nest type exprs.

I think solution (3) above can handle this though with some modification, especially because it redefines the term "type expr" to have nothing to do with operator precedence. Specifically, if an operand of "|" resolves to a type, throw early errors if the other operand doesn't resolve to a type. This would invalidate and throw early errors for the above "|" examples.

  1. type parameters: With the above changes, not sure if anything needs to be changed since type exprs are now a subset of value exprs.

I'm wrong here because that violates the "type exprs only nest type exprs" principle. Also, I meant to refer to "type arguments" rather than "type parameters". Is there any reason why type arguments should accept value exprs in a value expr context? If not, then this can be resolved by restricting type arguments to type exprs.

-Yuh-Ruey Chen