[Types-sig] Static typing: Towards closure?

Guido van Rossum guido@CNRI.Reston.VA.US
Thu, 20 Jan 2000 13:49:06 -0500


> From: scott <scott@chronis.pobox.com>

Scott forgot to cc this to the list, so I'm quoting him in its
entirety, with my comments interspersed.  Don't look for the original
message in the archives because it's not there.

> On Wed, Jan 19, 2000 at 05:49:53PM -0500, Guido van Rossum wrote:
> > > 			 *Constructors*
> > > your type proposal seems to mysteriously be missing the notion of 
> > > type constructors, that is, something like this:
> > > 
> > > datatype FOO = Int | None
> > > datatype BAR = Int | None
> > > 
> > > decl a: FOO
> > > decl b: BAR
> > > 
> > > a = 1
> > > b = a # ERROR!
> > 
> > Actually, in my mind, this would be allowed.  I believe in structural
> > type matching, not matching by name.  (That's what I've been
> > believing, anyway.)  Possibly when you introduce classes there's no
> 
> me too, in the general case.  i think most type checking in python
> should be structural,  but it is undeniable that allowing the user to
> produce completely new datatypes (rather than just compound ones) 
> can produce safer code:
> 
> datatype FIRSTNAME -> FIRSTNAME = string
> datatype LASTNAME -> LASTNAME = string
> datatype NICKNAME -> NICKNAME = string
> 
> def get_nickname_from_firstname(n: FIRSTNAME) -> NICKNAME:
>     # do stuff
> 
> def get_nickname_from_lastname(n: LASTNAME) -> NICKNAME:
>     # do stuff
> 
> 
> # To use this, you need a constructor in order to produce objects
> # with the new datatypes:
> 
> decl a: FIRSTNAME
> a = FIRSTNAME("scott")
> print get_nickname_from_lastname(a) # ERROR
> 
> By requiring the casts, there is also a good argument that it makes
> code more readable.
> 
> Personally, I wouldn't use this much in python even if it were
> available, I'd opt for 'structural' types 95% of the time.  But I'd
> appreciate it being available, and recognize its utility in
> appropriate circumstances.  It's mostly just something that comes from
> examining what's out there and why it's useful.

I think it's a nicety that's best left out of our initial design.  As
you say, it's not needed in 95% of the time.  And Python is kind of a
95% language... :-)

> > way to have another class match even if it has the same structure,
> > but for simple unions I don't see the need.  (Or is your "datatype" a
> > special declaration different from my typedef, which I spell
> > 
> > decl FOO = int | None
> > 
> > ?)
> > 
> 
> I believe the notion of 'datatype' could be put to many uses, defining
> types in many different ways.  One could use another term, or use lots
> of terms, but I am working under the assumption that lots of keywords
> are bad for compatibility reasons.

I currently (ab)use the decl keyword both for defining new types and
for declaring the types of objects; decl T = S defines a new type T to
be a new name for type S (which could be a type expression), while
decl v: T declares a variable v of type T.

This uses only one keyword and is unambiguous.  Can't get better than
that. :-)

> > > b = BAR(a) # ok, explicit cast or 'constructor'
> > > 
> > > It is possible to parse the cast at compile time, since it requires
> > > no knowledge of the value of `a', only its type.
> > > 
> > > The above example is slightly contrary to a grammar I put together, 
> > > and I don't believe it's the right way to denote constructors, but it
> > > does demonstrate the intended semantics.
> > > 
> > > using constructors can make dealing with logically OR'd types much
> > > easier and cleaner, and allows the user to define more specific types
> > > than allowed with builtin types.  I think it's quite a worthwhile pursuit.
> > 
> > I have chosen names for the std types that are the same as existing
> > built-in functions, because I want to get a similar effect.  E.g.
> > 
> > x = int(y)
> > 
> > is currently a call of the built-in function int() which happens to
> > return its argument converted to an int, or die with an exception; in
> > the new system, it would have the same effect but it would be a
> > constructor for the int datatype (and the built-in object int would be
> > the same object as types.IntType).
> 
> There is a semantic distinction between using 'int' as both a
> constructor and a type and the above example:  'int' the builtin
> function alters the value of its argument.  In the above, there's no
> mechanism for doing that -- it's only the type that changes.
> Nonetheless the similarity between the two is curious, and has
> potential for some nice continuity with the builtin typecast
> functions.

Of course, the builtin function doesn't alter the argument (how could
it -- it's probably an immutable object like a string) but I
understand what you mean: the returned object is not always the same
object as the argument.

This is following the example of C, where (int) and (float) casts can
change the value, in a sense, while typical pointer casts only change
the type.  This may be seen as a bad example, but I'm not so sure.

I think it's possible to live with the slight conceptual mismatch.
Maybe a more serious problem is whether this extends to classes: if C
is a class, and D is a derived class, we might want to be able to cast
between the two using the same notation:

decl myC: C, myD: D
myC = C(myD) # downcast
myD = D(myC) # upcast; runtime typecheck inserted

But the problem here is that C(x) and D(x) are already constructor
calls and may have very different meanings...

> > It's true that these constructors have something of a dynamic cast in
> > them (they can fail if the argument is unacceptable).  I think that in
> > my proposal,
> > 
> > x = int(a)
> > 
> > would have about the same semantics as Greg's
> > 
> > x = a ! int
> 
> The user defined casts wouldn't suffer from that problem:  they don't
> alter any values, just the associated types of the values.

What do you call a user defined cast?

--Guido van Rossum (home page: http://www.python.org/~guido/)