[Types-sig] PyDL RFC 0.01

Paul Prescod paul@prescod.net
Mon, 27 Dec 1999 06:01:05 -0500


Every one of your questions addresses an issue of ambiguity in the spec.
Thanks! I'll quote you pieces of the NEW and IMPROVED spec that your
comments generated.

scott wrote:
> 
> > An interface is a Python object with the following attributes:
> >
> > __conforms__ : def (obj: Any ) -> boolean
> > __class_conforms__ : def (obj: Class ) -> boolean
> 
> What is the rational behind separating __conforms__ and
> __class_conforms__? It seems to me like __conforms__ could do
> everything __class_conforms__ is supposed to.  Am I missing something?

Every interface object (remember, interfaces are just Python objects!)
has the following method :

__conforms__ : def (obj: Any ) -> boolean

This method can be used at runtime to determine whether an object
conforms to the interface. It would check the signature for sure but
might also check the actual values of particular attributes.

There is also a global function with this signature:

class_conforms : def ( obj: Class, Obj: Interface ) -> boolean

This function can be used either at compile time (e.g. by an
implementation of an interface checker) or runtime to check that a
class will generate objects that have the right signature to conform
to the interface.

> > Array( Integer, 50 )
> By `50', do you intend length of 50?

3. parameterize a type:

Array( Integer, 50 )
Array( length=50, elements=Integer )

> > const [const Array( Integer )]
> 
> By nesting const declarations, do you intend that checks against
> modifiability at runtime are shallow? For example, if I declare
> array A as a constant array of Foo instances, are those foo instances
> (or their attributes) modifiable?

Right. That's my feeling right now but I could probably be convinced
otherwise.

> Do you envision the import statement to be similar in syntax to
> regular python?  (eg from m import v; from m2 import *)
	
An import statement in an interface file loads another interface file.
The import statement works just like Python's except that it loads the
PyDL file found with the referenced module, not the module itself. (of
course we will make this definition more formal in the future)

> what do you mean by 'attribute declarations'?  I'd hate to see
> classes that couldn't have attributes that are parameterizable, but
> agree that resolving parameters needs to end somewhere.

I'm talking both about module, interface and class attributes. I think
that it is sufficient that a class' attributes can be parameterized and
can use class parameters. They don't need to be independently
parameterizable.

So this is allowed:

class (_X,_Y) spam( A, B ):
    decl someInstanceMember as _X
    decl someOtherMember as Array( _X, 50 )

    ....

These are NOT allowed:

decl someModuleMember(_X) as Array( _X, 50 )
class (_Y) spam( A, B ):
    decl someInstanceMember(_X) as Array( _X, 50 ) 

Because that would allow you to create a "spam" without getting around
to saying what _X is for that spam's someInstanceMember. That strikes
me as overly dynamic for a static type-check system (at least for
version 1).

> what do you mean by 'shared' in the above?  Are you referring to the
> distinction between class attributes and instance attributes?

Yes. But in retrospect the concept may not jibe very well with the idea
that there can be many classes that implement a particular interface.
There is no way to share state between all of these objects. I think
I'll take that out.

Here's the new section on Undefined:

The Undefined Object:
=====================

The undefined object is used as the value of unassigned attributes and
the return value of functions that do not return a value. It may not
be bound to a name. 

a = Undefined   # raises UndefinedValueError
a = b           # raises UndefinedValueError if b has not been assigned

Undefined can be thought of as a subtype of NameError. Undefined is
needed because it is now possible to declare names at compile time but
never get around to assigning to them. In ordinary Python this is not
possible.

The only useful thing you can do with Undefined is check whether an
object "is" Undefined:

if a is Undefined:
    doSomethingWithA(a)
else:
    doSomethingElse()

This is equivalent to:

try:
    doSomethingWithA( a )
except NameError:
    doSomethingElse

It is debatable whether we still need NameError for anything other
than backwards compatibility. We could say that any referenced
variable is automatically initialized to "undefined". Undefined is
sufficiently restrictive that this will not lead to buggy programs.

Undefined also corrects a long-term unsafe issue with functions. Now,
functions that do not explicitly return a value return Undefined
instead of None. That means that this is no longer possible

a = list.sort()

With Undefined, it will blow up because it is not possible to assign the
Undefined value. Before Undefined, the code did not blow up but it
also did not do the "right thing." It assigned None to "a" which was
seldom what was intended.

 Paul Prescod