need some clarification on compile-time type vs. run-time type
Yuh-Ruey Chen
maian330 at gmail.com
Fri Nov 9 23:24:40 PST 2007
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
More information about the Es4-discuss
mailing list