[Python-Dev] On Syntax Extensibility (again)

Samuele Pedroni pedronis@bluewin.ch
Sat, 07 Jun 2003 01:19:54 +0200


I thought python-dev archives would be a better place for this than my hd. 
Warning: terse.

I have come to the conclusion that I should agree with Guido:

- generators do not let really abstract over things like exception handling 
etc.
- some kind of very structured and Smalltalk-flavored syntax extensibility 
would be nice to have (not macros)
- people who affirm that Ruby is better because is OO from the ground up 
(???) and has blocks are annoying but have some point :):), although block 
semantics in Ruby are kind of idiosyncratic (modern smalltalks are much saner)
- while blocks are really not orthogonal with the rest of Python, the 
minimal orthogonal additions to the language, I managed to imagine, would 
absolutely not be natural, unless you find something like this natural:

def sum(upto):
   s = 0
   repeat(upto,_) where:
     _ = effect (inc): s+=inc
   return s

'where' should be obvious, 'effect' is to assigments what 'lambda' is to 
expressions, so s is not local to the effect but is 's' from the outer 
scope and that 's' will be rebound.

- * -

All keywords&syntax is tentative. Some details are sketchy. This stuff is 
likely implementable but not all of this is probably a good idea, my goal 
was more to see how far one can go once we have first-class blocks and we 
re-apply some other Python's "semantics" patterns together with them.

[!!! thought after writing all the rest: the notion of first-class blocks 
would naturally demand a scope-global __Return__ handler and one 
__Break__,__Continue__ handler for each 'while','for',  in all function 
scopes with 'on' definitions': I doubt this would be good style:

def f():
   on worker(i):
     if i==20: break
     print i
   for i in in range(40):
     worker(i)

but should probably be supported, at least the construct as such is orthogonal.
]

First, blocks as first-class citizen, 'on' definitions:

on <name>[(args)]:
   <suite>

'on' defs don't have their own locals, do not introduce a new scope but 
close over "outer" scope locals and even assigned to "locals" are free, 
[behavior of virtual 'BLOCK' definition], except for args (?).

Scopes with at least one 'on' inside define locally:

class __Return__(Exception?): pass
maybe:
class __Break__(Exception?): pass
class __Continue__(Exception?): pass

Then

on fcb(args):
   <suite>

is equivalent to:

BLOCK fcb(args): # BLOCK pseudo-def, see above for scoping rules
   <suite>
fcb.__Return__ = __Return__
# maybe
fcb.__Continue__ = __Continue__
fcb.__Break__ = __Break__


<suite> is compiled using following code transformations:
  value expr   ==> return expr # needs new keyword!, produce value

  return expr  ==> raise __Return__,expr # i.e. non-local return

  yield : not supported!!! (simple generators and first-class blocks do not 
mix)

maybe:
  break        ==> raise __Break__ # i.e. non-local break
  continue     ==> raise __Continue__ # i.e. non-local continue

- * -

'do' statement:

[x... ?=] do expr: [(args)]: # ?= stands for all possible assignment forms
   <suite>

equivalent to:

on _anonym[(args)]:
   <suite>
try:
   [x... ?=] expr(_anonym)
except __Return__,_ret: # if meaningful; _ret should be user-invisible
   return _ret.args[0]
except __Continue__: # if meaningful
   continue
except __Break: # if meaningful
   break

Example:

def list10(pfx):
  do repeat(10): (i):
    print pfx,i

<===>

def list10(pfx):
   class __Break__(Exception?): pass
   class __Return__(Exception?): pass
   class __Continue__(Exception?): pass
   BLOCK _anonym(i):
    print pfx,i
   _anonym.__Break__ = __Break__
   _anonym.__Continue__ = __Continue__
   _anonym.__Return__ = __Return__
   try:
     repeat(10)(_anonym)
   except __Return__,_ret:
     return _ret.args[0]

Some global built-in __Return__,etc. could do, but using locally defined 
ones, let people call _anonym inside a 'do' inside expr resulted callable 
getting the "expected" behavior.

Notice: a user accessible 'on' construct is not necessary for 'do' but see 
next.

- * -

Further:

~First-class explicit blocks are to complex user-defined statements what 
methods are to classes~

'do**' statement:

[x... ?=] do** expr: [(args)]:
   <suite>

semantics:

build dict _suite_dict out of <suite> (same as 'class' statement):

try:
   [x... ?=] expr(**_suite_dict)
except __Return__,_ret: # if meaningful, _ret should be user-invisible
   return _ret.args[0]
except __Continue__: # if meaningful
   continue
except __Break: # if meaningful
   break

expr resulted callable should use _suite_dict only in a read-only way (let 
the door open to some optimizations?)

examples:

do** tryfin: # contrived
   on try_:
     <suite>
   on finally_:
     <suite>


class C(object):
   v = do** property: # with suitable property def
     def get...: ...
     def set...: ...

Yes, do** is kind of an awful keyword.

- * -

Needed keywords: one for each of 'do','do**','on','value'

- * -

Possible further extensions, tentative syntax:

inside do** <suite>s allow for assigment/defs statements of the form:
   def [expr](...): ... # no here [] mean really '['']' not optional
   on [expr]...
   [expr] ?= ... # assignment forms

which would have more-or-less semantics:

   [expr] ?= ... <===> locals()[expr] ?= ...

although a special bytecode would be probably used. And then expr result 
would have to be called as in

expr(_suite_dict)

because non-string keywords are not supported and not simply ignored [at 
least now].

then:

do** except: # contrived
   on try_:
     ...
   on [ExcClass1]:
     ...
   on [(ExcClass2,ExcClass3)] (e):
     ...

If exec would work properly with subclasses of dict [that's not the case 
now] do** expr could have a __make_ns__ attribute so that a specialized 
kind of dict could be used (multi-key or remembering the order by which 
items were set, etc.)

_exv = expr
if hasattr(_exv,'__make_ns__'):
   _ns = _exv.__make_ns__()
else:
   _ns = {}
exec <suite-code> in _ns
try:
   _exv(_ns)
   ...

That's all.