[Types-sig] PyDL RFC 0.02

skaller skaller@maxtal.com.au
Tue, 28 Dec 1999 10:37:52 +1100


Paul Prescod wrote:
> 
> PyDL RFC 0.02
> 
> A PyDL file declares the interface for a Python module. PyDL files
> declare interfaces, objects and the required interfaces of objects.

	Please stick to a syntax which can _also_ be embedded
in .py files. In this case, an interface file is ordinary Python,
except that it consists only of compile time directives.

	If I understand you correctly, interface files
are used to provide module interfaces: there is no other
sensible way to do that at present, since .py files
define modules.

	IF there were a way to create modules like:

	module X:
		# stuff that normally goes in a module file

in python, then there would be a corresponding

	interface module X:
		# stuff that normally goes in a module interface file

In other words, interface files should be regarded as an _artefact_
of the existing 'lack of syntax for defining a module'.
[Which Viper may correct :-]
	
On this basis, some comments:
 
> Interface definitions are similar to Python class definitions. They
> use the keyword "interface" instead of the keyword "class".
> 
> Sometimes an interface can be specialized for working with specific
> other interfaces. For instance a list could be specialized for working
> with integers. 

	No. I think you have to make up your mind here.
You must choose. Either 'List' is an interface,
or, it is an interface generator, it cannot be both.
[In your terminology, you can't use a parameterised interface
where a fully resolved one is required; so List cannot
be both partly unresolved and also fully resolved]

> In addition to defining interfaces, it is possible to declare other
> attributes of the module. Each declaration associates an interface
> with the name of the attribute. Values associated with the name in the
> module namespace must never violate the declaration. Furthermore, by
> the time the module has been imported each name must have an
> associated value.

	OK. This is the crux of the semantics: you are
applying interfaces to names, rather than values/objects.

> The interface interpreter reads the PyDL file and builds the
> relevant interface objects. 

	Furthermore, the Python compiler will do it too;
that is, it will process embedded interface specifications.

>If the PyDL file refers to other modules
> then the interface interpreter can read the PyDL files associated
> with those other modules. 

	Yeah, but you would do well to get out of the habit
of saying 'can' and 'may'. Use the word 'shall'. Meaning,
that the damn thing is REQUIRED to do something :-)
Dont give permission. Specify requirements.

> The interface interpreter maintains its own
> module dictionary so that it does not import the same module twice.

	That's better, but should be marked as 'commentary',
since it has no semantic implications.
 
> Interface expression language:
> ==============================
> 
> Interface expressions are used to declare that attributes must conform
> to certain interfaces.  In a interface expression you may:

	Do NOT say 'may'. Do not refer to 'you', the programmer,
we're not interested in what the programmer does, we're interested
in what the interface compiler does. And it SHALL interpret
certain grammatical constructions in a particular way,
no 'may' about it.
 
--

	Point 0: Paul, list the predefined names like Integer,
	or whatever. Say if they are keywords or plain identifiers.

	Use a grammar production like:

	basic_if_name ::= "Integer" | "Float"


> 1. refer to a "dotted name" (local name or name in the PyDL of an
> imported module ).

	This doesn't make any sense to me.
 
> 2. make a union of two or more interfaces:
> integer or float or complex

	Give the grammar. EG:

	if_alt ::= if_name "or" if_alt | if_name

> 3. parameterize a interface:
> 
> Array( Integer, 50 )
> Array( length=50, elements=Integer )

	grammar?
 
> Note that the arguments can be either interfaces or simple Python
> expressions. A "simple" Python expression is an expression that does
> not involve a function call.

	No. See above. List(Int) already involves a 'function call'.

> 4. use a syntactic shortcut:
> 
> [Foo] => Sequence( Foo ) # sequence of Foo's
> {A:B} => Mapping( A, B ) # Mapping from A's to B's
> (A,B,C) => Record( A, B, C ) # 3-element sequence of interface a,
> followed
>                              # by b followed by c

	Forget this, for the moment. Add syntact sugar
later, when the core grammar and semantics are more settled.
 
> 5. Declare un-modifiability:
> 
> const [const Array( Integer )]
> 
> (the semantics of un-modifiability need to be worked out)

	Again, forget it, for the moment.
This one can be real nasty.
 
> Declarations in a PyDL file:
> ============================
> 
> (formal grammar to follow)
> 
>  1. Imports
> 
> 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)

	No. Use a distinct keyword like 'include'.
There is a good reason for this: consider embedded declarations.
Then it is 

	a) impossible to load an interface but not the module
	b) impossible to load a module, but not the interface

A separate keyword resolves the ambiguity when embedded:

	import X # load the module
	include X # load the interface

Note that importing a module implicitly loads the interface anyhow.
However, it will do so in an appropriate namespace.	

It is necessary to load interfaces even when modules
are not imported (by the client module). There are other
ways to get at stuff from a module than import it.
For example, a function call f() can return an object whose
class is defined in a module X the calling module has
not imported: we may want to type check the returned
object, which requires importing the module X's interface
-- without importing the module X itself.

>  2. Basic attribute interface declarations:
> 
> decl myint as Integer                   # basic
> decl intarr as Array( Integer, 50 )     # parameterized
> decl intarr2 as Array( size = 40, elements = Integer ) # using keyword
> syntax
> 
> Attribute declarations are not parameteriable. Furthermore, they must
> resolve to fully parameterized (not parameterizable!) interfaces.

	grammar. Again, distinguish interfaces from
interface generators, and the above ambiguity in the wording
disappears.
 
>  3. Callable object interface declarations:
> 
> Functions are the most common sort of callable object but class
> instances can also be callable. Callables may be runtime parameterized
> and/or interface parameterized.  For instance, there might be a method
> "add" that takes two numbers of the same interface and returns a number
> of
> that interface.
> 
> decl Add(_X: Number) as def( a: const _X, b: const _X )-> _X
> 
> _X is the interface parameter. a and b are the runtime parameters.

	I am already using ! not : here, following Greg Stein.
There are enough ":"'s in python already :-)

	OTOH, I am also using 'as' in another context :-(

>  4. Class Declarations
> 
> A class is a callable object that can be subclassed.  Currently the
> only way to make those (short of magic) is with a class declaration,

	They can be created in extension modules.

> Here is the syntax for a class definition:
> 
> decl TreeNode(_X: Number) as
>         class( a: _X, Right: TreeNode( _X ) or None,
>                     Left: TreeNode( _X ) or None )
>                 -> ParentClasses, Interfaces

	Hmmm. Confusing me. Especially the newline after 'as'.
Python requires brackets, or a colon, to start a newline,
you can't do it in the middle of a statement.
 
> What we are really defining is the constructor. The signature of the
> created object can be described in an interface declaration.

	Not good enough. The semantics of class instance
attributes would be 'when you assign to this attribute,
it had better have this type'. This doesn't mean that
you can be sure an access gives that type,
the attribute might not exist. This defeats optimisation.

	You'd need to say something like: AFTER the 
constructor has run, and BEFORE the destructor has run
the attribute exists and has the designated type.
Enforcing that might be tricky :-)
 
>  5. Interface declarations:
> 
> interface (_X,_Y) spam( a, b ):
>     decl somemember as _X
>     decl someOtherMember as _Y
>     decl const someClassAttr as [ _X ]
> 
>     decl const someFunction as def( a: Integer, b: Float ) -> String

	Semantics?
 
> 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. 

	No. All Python functions return a value. If one is not
returned explicitly, None is returned implicitly. People
check for that: for example:

	def f(x): 
		if x in [1,2,3]: return x

	if f(99): print 'Got it'
	else: print 'Not 1,2 or 3'

Your spec would break this code. You can argue that your
spec is a better spec -- but it isn't Python compatible.

	FYI: In Viper, uninitialised, statically
declared variables are initialised with the special object PyInitial.
Another special object, PyTerminal, also exists. These objects
are useful in the internal workings of the implementation,
for bounding things (i.e. as sentinels). For example,
it makes calculating max( .... ) much easier. [PyInitial
is less than all other objects]

> Undefined also corrects a long-term unsafe issue with functions. Now,
> functions that do not explicitly return a value return Undefined
> instead of None. 

	No. That would break compatibility.

> Experimental syntax:
> ====================
> 
> There is a backwards compatible syntax for embedding declarations in a
> Python 1.5x file:
> 
> "decl","myint as Integer"
> "typedef","PositiveInteger as BoundedInt( 0, maxint )"

	Nice.
 
> Summary of Major Runtime Implications:
> =====================
> 
> All of the named interfaces defined in a PyDL file are available in the
> "interfaces" dictionary that is searched between the module dictionary
> and
> the built-in dictionary.

	I _think_ you mean that the interface dictionary 
is 'per module'? And you can refer to an interface 
in another module with other.interfx notation?
 
> The runtime should not allow an assignment or function call to violate
> the declarations in the PyDL file. In an "optimized speed mode" those
> checks would be disabled.

	I think you have to think very carefully about what
constitues an error here: see my posts about errors in python.
It is not acceptable to specify that an exception be thrown.
That would NOT permit an optimiser to elide checks, except
when it could prove they were not needed.

	Much better, you deem a violating program
is not valid, and then the language processor can do whatever
it wants: it may raise an exception, or it may core dump,
or it may reject the program early.
 
-- 
John Skaller, mailto:skaller@maxtal.com.au
10/1 Toxteth Rd Glebe NSW 2037 Australia
homepage: http://www.maxtal.com.au/~skaller
voice: 61-2-9660-0850