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

Yuh-Ruey Chen maian330 at gmail.com
Sun Nov 11 15:09:29 PST 2007


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:
>
> http://wiki.ecmascript.org/doku.php?id=proposals:meta_object
>
> See also:
>
> http://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 http://wiki.ecmascript.org/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...
>
> http://wiki.ecmascript.org/doku.php? 
> id=meetings:minutes_sep_27_2007#proposal_walkthrough
>
> Ticket 103: http://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



More information about the Es4-discuss mailing list