[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Optional types

Let me take a break from a weekend of Proto hacking and try to answer
some of these optional type questions from a Dylan perspective.

On Fri, 2001-12-07 at 18:10, Paul Prescod wrote: 
> Eric Kidd wrote:
> > 
> > 1) Every object belongs to a class.  Class membership--even for
> > parameterized classes--can be tested in a handful of instructions. 
> Here's the problem in the Python world. Python people are very used to
> being able to say: okay, X is not *really* a Y, but it looks enough like
> a Y to pass. Certainly this is common in any dynamically typed language.
> Now imagine module implementor decides to define an interface using
> static declarations for all of the maintenance and performance benefits.
> All of a sudden, the programmer can't pull this looks-like-a trick
> anymore.

This is nice, but it has major repercussions: 

1) You no longer have any real notion of "types".  Instead, you have a
vaguely-defined notion of interfaces.  An object matches an interface if
it defines the right methods (perhaps even by abusing __getattr__ to
generate the necessary methods at runtime).  So arguably, there's not
much point in wanting type declarations in such a language. 

2) Insofar as you have types, you can't check them quickly. 

3) Optimization gets nasty.  You certainly can't optimize method
dispatch to within an epsilon of C++, for example, without bleeding out
your ears.  (Well, maybe David Simmons can, but I can't.)

Now, I gather that David Simmons' runtime does an amazing job of
addressing some of these issues.  But what if we actually *wanted*
types, at least in some places in our program? 

Let's look at how Dylan handles optional types.  (And, yes, I know the
Dylan syntax is odd by modern standards.)

A) Type declarations are optional, and default to <object>. 

  let x = 10; 
  let x :: <object> = 10;  // same as above 
  let x :: <integer> = 10; 

B) If a type declaration can't be proven at compile-time, a runtime type
check is inserted.  Dylan compilers generally do not warn when inserting
type checks, and some Dylan interpreters leave all type checks to

  let x :: <integer> = get-a-string();  // runtime error 

C) "Downcasting" is automatic, and inserts a type check as per (B). 

  let x = get-a-collection(); 
  size(x);  // automatically checked 

D) Parameterized types are bit weird.  Most collection classes exist in
parameterized and unparameterized versions.  You can't fill an
unparameterized collection with a given datatype and treat it as a
parameterized collection.

  let x :: <simple-vector> = #[0, 0, 0];
  let y :: limited(<simple-vector>, of: <integer>) =
     make(limited(<simple-vector>, of: <integer>), size: 3, fill: 0);
  y := x; // Does not work.
E) Because of (D), types such as 'limited(<simple-vector>, of:
<integer>)' can be stored as (essentially) a C array plus a length field
and some type information.  So once we see such a type declaration, the
optimizer can go nuts, and produce screamingly fast code without too
much work.

F) Dylan uses #f (false) in the place of Java's 'null'.  But it can only
stored in variables of the appropriate type:

  let x :: <foo> = #f;           // Error
  let y :: false-or(<foo>) = #f; // OK

> What do existing mixed-mode languages do to allow this style of
> programming?

G) If you want to create smart data, you can do it by subclassing.  For
example, if you want a hash table that contains the current process
state (Dan's example), you make a subclass of <table> and override the
appropriate methods.

This allows you to get most of the advantages of smart data without
quite as many optimizer headaches.  And without too much trickery, you
can eliminate bounds checks, and push performance to within (say) 80% to
90% of C.

Dylan declares certain classes, such as integers, strings and floating
point numbers, to be 'sealed'.  (It's similar to 'final' in Java.) 
These classes can't be subclassed, so you can't have (say) smart
<integer>s.  (You can still have smart <number>s, of course.)  This is
an interesting compromise--extensibility in some places, but not in
places you need to go fast.

Proto, on the other hand, will allow you to subclass primitive types. 
The plan is for the runtime to recompile highly optimized functions into
a more general form.

But this requires me to finish the dependency tracker, so I have to go
now. :-)