[Python-Dev] Re: Extended Function syntax

Guido van Rossum guido@python.org
Wed, 29 Jan 2003 23:49:05 -0500


> Maybe something like this would be nice:
> 
>     xdef myprop('myprop docstring') [property]:
>         def __get__(self):
>             ...body...
>         def __set__(self, value):
>             ...body...
> 
>     xdef frame1() [SunkenFrame]:
>         xdef button1('OK') [Button]:
>             def ClickEvent():
>                 ...body...
>             def GotFocus():
>                 ...body...
>             MouseoverText = 'Hello!'
>         xdef text1(datasource=xyz) [TextBox]: pass
> 
>     xdef klass(base1, base2) [interface1, interface2]:
>         ...class body...
> 
> This is almost exactly what Arne Koewing posted:
> 
>     blockdef myprop [property]:
>         def __get__(self):   
>             ...
>         def __set__(self, value):
>             ...
>         def __delete__(self):
>             ...
> 
> Except I tried to make it look a bit more like 'def'.

But why require the empty ()?  It is not a function definition.

I think the property docstring should go in the usual docstring
position.  I also think that there's not much point in requiring
__get__ etc.; get works just as wel.

  xdef myprop [property]:
      "myprop docstring"
      def get(self): ...

> Obviously, this would go hand-in-hand with 'def' being extended to
> allow a list of modifiers at the end, like this:
> 
>     def name(args) [staticmethod]:
> 
> The general syntax for 'xdef' would be something like:
> 
>     xdef name(arg1, arg2, kw_arg1=x, kw_arg2=y) [mod1, mod2]:
>         ...class-like body...

Let's discuss evaluation order a bit.  It sounds like the arguments
should be evaluated first, then the modifiers.  (I propose that the
argument list, if present, has exactly the same syntax as a function
call argument list.)

Then there are two possibilities for the body:

(1) it could be evaluated before the xdef statement is "executed"
    (just as a class body);

(2 or it could be compiled into a "thunk" which is somehow passed to
    whatever is called to implement the xdef (like a function body).

Variant (2) gives much more freedom; it would allow us to create
functions that can be called later, or it could be executed on the
spot to yield its dictionary.

> So this would work like:
> 
>     name = mod2(mod1(name, args, kw_args, dict))

Variant (2) would have the thunk object instead of dict.

I think a thunk should be a callable; upon execution it would return
its dict of locals.

Maybe if a thunk contains a 'return' statement it could return the
return value instead.

Maybe someone can find a use for a thunk containing 'yield', 'break'
or 'continue'.

Maybe all control flow in the thunk should raise an exception that the
xdef can catch to decide what happens.  Hm, if it did 'yield'
you'd like to be able to call next() on it.  So maybe there should be
different types of thinks that one can distinguish.

> I guess args and kw_args would work something like how 'bases' are
> collected in class definitions before being passed to metaclass
> __new__ and __init__.

Sure.

> 'xdef' could return a class just as well as a property:
> 
>     xdef klass(base1, base2) [interface1, interface2]:
>         ...class body...
> 
> 'interface1','interface2' would have to be clever about sometimes
> being passed a class and sometimes being passed the tuple (name, args,
> kw_args, dict).

How does xdef know whether it's a class or not?  I like it better if
we're definite, and choose (2) always.

> In general, 'xdef' would allow metaclass-like processing on things
> that are not necessarily a class.
> 
> Problems:
> 
> (1) The main problem is that 'xdef' makes things that are pretty close
> to being classes.  So why not just use classes?

Because they're not classes.  Let's not follow C++'s example of
avoiding new keywords at all cost and stretching the meaning of some
existing ones beyond the bounds of reason.

> (2) Also 'xdef' is pointless without [mod1, mod2]
> 
>     xdef name():
> 
> would just assign the tuple (name, args, kw_args, dict) to name.

Or maybe it would be illegal, or maybe there would be some default
modifier.  (Like class has a default metaclass.)

> (3) 'xdef' isn't a great name, because
> 
>     xdef abc(xyz):
>     def abc(xyz):
> 
> do completely different things.  But the name should be short like
> 'def' so it doesn't draw much attention to itself, just as 'def'
> doesn't draw much attention to itself. So
> 
>     xdef myprop() [property]:
> 
> the thing that sticks out is 'property'

Let's just say 'xdef' is just a shorthand for a keyword we haven't
picked yet.

> More bad stuff:
> 
>     xdef abc(xyz):
>         return 7
> 
> would raise a 'SyntaxError: 'return' outside function'

See above.

>     xdef name(arg1, kw_arg1=x) [mod1, mod2]:
>         y = arg1 + kw_arg1
> 
> would raise NameError.

Not necessarily.  Maybe the thunk can somehow keep a reference to the
arguments (like nested scopes?).

One think I'd like to do with extensible syntax like this (and for
which xdef is a poor name) would be to define a "lock" statement, like
Java's "synchronized" block.

Maybe it could be written like this; I made the name after xdef
optional:

  xdef (mylock) [synchronized]:
      ...block...

which would mean:

  mylock.acquire()
  try:
      ...block...
  finally:
      mylock.release()

It would be nice if control flow statements (return, break, continue)
inside the block would somehow properly transfer control out of the
block through the finally block, so that you could write things like

  def foo():
      for i in sequence:
	  xdef (mylock) [synchronized]:
	     if ...condition...:
		 continue
	     ...more code...
	     if ...condition...:
		 break
	     ...yet more code...
	     if ...condition...:
		 return 42

Only 'yield' doesn't really fit in here (like it doesn't fit in lots
of places :-).

Though this would look much better if synchronized came before
mylock.

It is also unsatisfying that you can't easily extend this to take
multiple blocks.

Maybe we can change the syntax back to something that started this.
Instead of the xdef keyword, we'd use a colon at the end of an
expression or assignment line.  The grammar could be e.g.

  statement: (variable '=')* expression [':' suite]

(Where suite is a simple statement or NEWLINE INDENT block DEDENT.)

Then the locking example could be written as

  synchronized(mylock):
      ...block...

This would mean

  synchronized(mylock)(thunk)

where thunk would be a callable representing the block as before.  If
a variable was present it would receive the result, so that e.g.

  foo = property:
    def get(self): ...

would end up meaning

  foo = property(thunk)

which is only a very small stretch from what property currently does
(it would have to do a typecheck on its argument for backwards
compatibility).

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