Ian Bicking: the old part of his blog

A Declarative Syntax Extension

After reading Guido's second static typing post, I got to thinking about syntax and interface and whatnot. This is the syntax Guido proposes:

interface I1(I2, I3):
    "docstring"

    # declare a method, with argument and result types and input validation code
    def method1(arg1: type1, arg2: type2) -> resulttype:
        "docstring"
        ...statements to validate input values...

I'll ignore the type declarations. He actually extends interfaces to support contracts (as the body of the interface methods), which is a cool idea. There's a question about how to validate the return type, off the top of my head:

interface IFile:
    def read(length=None):
        assert length is None or isinstance(length, int)
        v = return
        assert isinstance(v, str)

But that's an aside. What interests me is the interface. Interfaces aren't the only place where class is currently being abused. Consider this metaclass, for instance:

class propmeta(type):
    def __new__(meta, className, d, bases):
        if bases == (object,):
            return type(meta, className, d, bases)
        value = property(d.get('get'), d.get('set'), d.get('del'),
                         d.get('__doc__'))
        return value
class prop(object):
    __metaclass__ = propmeta

class Foo(object):
    class dynattr(prop):
        def get(self):
            if not hasattr(self, '_dynattr'):
                self._dynattr = self.calculate_dynattr()
            return self._dynattr
        def set(self, value):
            self.set_dynattr(value)

prop is a cute little class, a bit of syntactic sugar that isn't strictly "syntax"; of course, it's not really a class, it's a class statement abused into returning a property descriptor. There's several other places where people are doing this (now), and several more that would be possible. So, how might this be generalized?

This is where something that feels a little like a macro comes into play. Imagine when you see a statement like:

interface I1(I2):
    def blah(...): ...

interface is an actual object. A method of interface is called with the name ("I1"), the arguments ((I2,)), and a dictionary of defined attributes in the body (blah). And maybe a list of strings indicating what order, because that would be nice to know. This is very much like metaclasses, but without the keyword class.

Anyway, it's then up to interface to do what it wants. In this case it creates an interface object. class creates class objects, property creates properties, contact creates methods or functions with pre- and post-conditions, and so on. (Do these all always take a name -- class name, function name, property name -- and always add that name to their scope?).

Here's an example with contracts; it's not great, but it's not too bad:

contract gcd:
    def pre(a, b):
        assert isinstance(a, (int, long)) and a > 0
        assert isinstance(b, (int, long)) and b > 0
    def gcd(a, b):
        ...
    def post((a, b), value):
        assert isinstance(value, int) and value > 0
        assert not a % value and not b % value

You aren't transforming source, you don't even have lazy expressions. The mechanics are relatively simple to imagine. You won't confuse accidentally-adjacent expressions for a declaration, as the line has to end with :. E.g., these will still be a parse error:

call_func(arg1 # <-- oops, forgot comma
          arg2)
func 10

I want to call these new objects declarative keywords, but that's exactly what they aren't. I think the declarative objects should define some special name. Like __declare__ or something, rather than using __call__, which many objects already have, or __new__ which is a method I've always hated (maybe __new__... I dunno). If you, say, misspell class, you should notice quite quickly, as declarations typically occur at the global scope, and are executed on import, so errors (like misspellings) are quickly detected even though you've lost a class of syntax errors.

I don't like that you could start passing these objects around, e.g., using class in an expression. Not that I expect people will do that; higher-order higher-order syntaxes are clearly not a good idea.

I feel like there's a lot of potential uses for this. At first I thought these constructors should be passed more abstract code objects, and their entire containers would be lazily evaluated -- even def could be implemented in these terms then -- but that just seemed too complicated and prone to complexity. But then things like contract could be much slicker with that ability.

Created 07 Jan '05
Modified 26 Aug '05

Comments:

so will python eventually become like Boo? (boo.codehaus.org) There is already an interpreter and a decent compiler for Boo and it run on .NET and mono. And using it in SharpDevelop is really nice.
# fxj