syntax for preset locals without using dummy args with defaults

Beni Cherniavsky cben at techunix.technion.ac.il
Mon Jan 13 11:17:35 EST 2003


On 2003-01-11, Bengt Richter wrote:

> My first choice would be a new keyword to provide a place other than the parameter
> list to specify the def-time code that generates the local bindings in question
> (e.g., z above):
>
>    def foo(x, y=default):
>        preset: z=some_expression
>        ...
>
> IOW, the suite of "preset:" would be compiled to be executed at def-time
> rather than call-time. I.e., the code would go into the same context as
> the code that evaluates default parameter expressions, not into the body
> of the call-time function code. Having a full suite would allow exception handling
> and raising in that context, and uncaught exceptions would propagate the
> same way def-time default value expression exceptions do.
>
That's precisely the problem with it.  Consider the lisp/scheme way to
create functions with preset bindings, (including the "static" behaviour,
since python's restrictions against changing bindings in outer scopes
don't apply):

(let ((z zome_expression))
  (define (foo x y)
    ...))

Note that the binding is computed when the function is created and not
when it's run - and that's precisely how it's written.  Currently I can
blindly assume that the body of a def is not executed immediately.  With
your preset syntax, I must be wary when reading defs.  It'd be like lisp's
backquote, a.k.a. semi-quote (which allows special markers inside the
quoted expression to "unquote" parts of it) that requires more attention
from the reader than simple quoting.

Therefore I would only consider a syntax where the "presets" are outside
the def body.  The solution:

def foo():
    z = some_expression
    def foo(x, y=default):
        ...
    return foo
foo = foo()

satisfies me currently if I would want to pre-compute anything.  True,
the outer should be an unnamed lambda and the combined with the fact
that it's executed once, on the spot, it's precisely a let form.  How
about:

let z = some_expression:
    def foo(x, y=default):
        ...

The intention is that `foo` is still defined in the global namespace.
Only `z` has a local scope.  See point 3 below.

The problems:

1. Most would probably consider this a YAGNI for adding a new keyword.

2. I see no elegant syntax to allow multiple bindings on the same level
   (e.g. let and not "poor-man's let*").  For multiple bindings that
   are not inter-dependent, nesting the lets is visually sub-optimal...
   The closest thing in python for simultatneous bindings is augmented
   assignment but it was not designed for this case (no var-value pairs
   ordering and doesn't continue for many lines.

3. The same reason why Python avoids nesting scopes in e.g. a for loop:
   currently, the scope-creating constructs (def & class) make all
   inner assignments have a new scope.  So introducing anything that
   behaves otherwise would make scoping less consistent and "harder to
   explain".  On the other hand, it would also make it much easier to use
   and I believe the later should win.

> Of course preset: would allow single-line or indented suites like any compound
> statement.
>
That means that you allow not only assignments in the preset section.  I
guess the semantics are just to freeze the locals at the end of the preset
section and continue from there every time the function is called.  That's
nice, reminds iterators:

def foo():
    z = some_expression
    yield None	# dummy
    ...
foo = foo()
foo.next()	# execute preset
foo = foo.next	# the continuation in callable form

Alas, the resulting code can't accept parameters at call time and
more importantly, it can only be called once...

> I think if it became a convention to set up bindings to module elements
> etc, and do other expensive one-time things in the preset: suite, we would
> see both less clutter in the function code body and better speed.
>


> Conceivably on could write a source-transforming optimizer to hoist builtin
> dotted module references into the preset suite, as a start. Maybe with
> some hints it could hoist other global refsas well. It might be an
> alternative to complex runtime global access optimizations.
>
They are not that complex, especially PEP 267.  If the only use for this
is proving clean syntax for manual optimization of global lookups, I'm
against it.  I do think that a let construct could be very useful beyond
optimizations, as I said above.

> ==[1]============================================================
> Persistent local bindings would also be useful.
[snip]
> What's needed for persistently rebindable local names is a way to
> differentiate the names. I think just prefixing a dot would be concise
> and simple, e.g.,
[snip]
> There, now we have the best of both worlds ;-)
>
> BTW, you could implement .xxx as foo.xxx, but it would be more efficient to
> collect all the dot-names and do a __slots__ kind of thing with them, IWT.
>
I surely would rathen write the static variables as attributes of the
function.  Clean and reable.  But using static variables is the best way
to ask for trouble, anyway.  Use an object or factory function as you say
below.

> Of course, you probablywouldn't want to use persistent bindings directly
> in e.g., library functions but you mightwant to make a factory function that
> returns configured function instances which would have separate persistent locals.
>
But then where are they stored?  This is very easy to implement with
current functions and the standard boxing idiom for changing values
in outer scopes:

def make_foo(z):
    z = [z]
    def foo(x, y=default):
        ...
        z[0] += 1
        ...

Except that it's ugly.  A class would be more elegant here I think.

-- 
Beni Cherniavsky <cben at tx.technion.ac.il>

Holy resolution for a holy war: the Torah stores most numbers as
little-endian (e.g. "seven and twenty and a hundred years")!







More information about the Python-list mailing list