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