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

Yuh-Ruey Chen maian330 at
Sat Nov 17 01:54:29 PST 2007

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.
> > 2) 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.
> > 3) 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.
> > 4) 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.
> > 5) 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:

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

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:

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

More information about the Es4-discuss mailing list