need some clarification on compile-time type vs. run-time type
Yuh-Ruey Chen
maian330 at gmail.com
Sun Nov 11 22:41:47 PST 2007
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?
> > 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.
>
> 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
>
> http://wiki.ecmascript.org/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).
>
> > 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.
>
> 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.)
>
> > 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.
>
> 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.
>
> > 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.
>
> 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
>
> http://wiki.ecmascript.org/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
More information about the Es4-discuss
mailing list