Re: Lambda binding solved?

Tim Peters (tim@ksr.com)
Wed, 02 Mar 94 01:36:57 EST

Plug: I hope the recent volume of traffic here has motivated everyone to
vote YES on comp.lang.python! It's your civic duty <smile>.

> [steve m]
> ... maybe that other route is pointed to by the fact that I couldn't
> create a new function with different bindings in my kludge/fix, but had
> to modify an existing functions attributes.

I think you were right there that Python has all the support "inside" for
supporting closures, just lacking a way for user code to directly fiddle
a function's namespaces. OTOH, even if that machinery were all exposed,
I wouldn't want to have to explicitly fiddle with it just to get a simple
lambda to work. Leave Scheme to the schemers ...

> And that you can't create a module without importing a file.

Is this what you were getting at with your comment about making modules
more of a "1st class environment"? Old threads are tugging at my failing
memory ...

> ...
> Also: I think CSNS could be disastrous combined with Python's
> "create it if it ain't there" assignment and lack of declarations!

Alas, those interactions are much harder for a human to keep track of
than a compiler. E.g., given CSNS, Python will have no trouble at all
realizing that the "i" referenced in "g" belongs to "f":

i = 12
def f(...):
def g(...):
return i
# 60 lines of code omitted
i = 4
# 60 lines of code omitted
x = g()
# 3 lines of code omitted

A _person_ reading that code isn't likely to make the right guess; but
today, without CSNS, we only need to look at g's body to know that "i"
must be global or builtin.

> ...
> How many namespaces are in your $PATH or $PYTHONPATH shell variable ?

Way too many (of course), but PATH is a lousy design for name management!
PATH=p1:p2:p3 is very much like doing

from p3 import *
from p2 import *
from p1 import *

in Python, and prone to all the same unintended & silent name-collision
errors. How many hours have you spent tracking down "mysterious
failures" due to some newbie not knowing that (e.g.) /usr/local/bin was
supposed to come before /usr/ucb in their PATH else they'll get "the
wrong" version of (e.g.) zcat? But then I never use "import *" except
for interactive one-shots.

> ... but search paths for default values are just one of those cases. It
> may be possible to set a global value ( in __main__ ), a module wide
> value ( for everything defined in that module ), for a classes of a
> specific type in that module, or for a particluar instance of that
> class. And if none of them are set, then use the default defined in
> module 'sys' or module 'defaults'.

Just don't forget the environment variables to reorder the default search
paths, and the other ones to augment them, and ... <wink -- but
"possible" != "desirable"; then again, I don't know your specific
application here>.

> ...
> getattr( instance, 'thing' ) returns thing if it is bound in either
> the class or the instance. ( And, if you think about it, is why
> self.method() [ method is in the Classes __dict__ ] and self.ivar
> [ ivar was set in self.__init__, and is in the Instances __dict__ ]
> both work.

I can count to two in my head most days, so those are OK <0.9 grin>.
It gets more complicated in, e.g.,

class A: a = 1
class B(A): pass
class C(B): pass
class D(C): pass
x = D()
hasattr(x,'a') # returns true

so I try to avoid stuff like that.

> ...
> In any event, here are two utility functions.
> [some nice stuff deleted]
> ...
> But can anyone think of another case where one might want, for example,
> to invert the search space and have global names have precidence over
> local names ?

No help from me! The code is slick, but I still don't grok why you're
doing it. It _sounds_ like you're trying to a model an application's
name mappings by reflecting them in Python's runtime structures (instead
of, e.g., modeling them as ordinary dicts). I believe I'm lost here.

> ...
> I forgot about __builtin__, or assumed that it was not possible to add
> names into that module, but I *was* able to:

Which is one of the reasons Python can't resolve global-vs-builtin at
compile-time. Here's a stupid trick for finding the first time your
application calls "open" <wink -- but I did this once!>:

import __builtin__
del __builtin__.__dict__['open']

> ...
> The problem is that is often doesn't LOOK like static scoping.
> [ I was about to insist that it ISN'T static scoping, but I see
> that the fact that functions carry their module global's environment
> around with them makes it static scoping. Maybe it's a hybrid? ]

Well, if we took time to define the terms carefully, it would answer
itself. It's purely static scoping as I understand the term, and pretty
clearly so if you stay away from the runtime-compilation constructs
(eval, exec, compile, ...).

> ...the problem [below] is that it LOOKS reasonable to anyone who isn't
> intimate with the scoping rules.
> ...
> >>> def makeinc( n ):
> ... class Incr:
> ... def incr(self,x) : return x + n
> ... return Incr().incr
> ...
> >>> a = makeinc( 4 )
> >>> a( 3 )
> Traceback (innermost last):
> File "<stdin>", line 1
> File "<stdin>", line 3
> NameError: n
> >>> n = 1
> >>> a( 3 )
> 4
> >>>
>
> Like the lambda problem, this does not necessarily have to be
> interpreted as a requirement for nested scope. In both cases,
> what I *wanted* was the VALUE of 'n', not the name, but there
> was no way ( that I could think of! ) to NOT defer evaluation
> of 'n' and use it's value.

I think everyone gets burned by this once, at which point they're
motivated enough to really study the reference manual. That and the
FAQ's question 4.5 lead to the realization that there's a _general_ way
to solve these problems, using classes. At that point they've reached
Python puberty ... it's a rite of passage.

Note: even under CSNS, there's no assurance the example above would
"work" -- different languages make different decisions about whether a
variable's lifetime can exceed the time control is in its scope.

> ...
> So, other than support for nested namespace, I see three sorts of
> "fixes" ( in quotes, because 2 of them are really "work-arounds" ):
>
> (1) propagate the local namespace ( perhaps by copy, so that
> "lambda x: n = n + x", if allowed, would still do nothing
> ( although "lambda x: n.append( x )" probably would. )

Guido will never suck for this one, and I bet neither of us _really_
likes it either <wink>.

> (2) value(n) or constant(n) - they only look like functions !
> ( so I guess that could be "value n" without the parends),
> they are statements that force evaluation of the current
> value of n. This would still confuse folks with code that
> LOOKS like it ought to work, but would at least give them
> a simple alternative way to do it.

I don't grasp your intent here. E.g., in your example above, where would
you put this new fnc/stmt? If inside class Incr, and without also
changing the scoping rules, the "n" in "value n" would still refer to
global (or builtin ...) "n". In other words, yes, I understand that you
wanted to capture the _value_ of n inside def incr, but since you can't
_see_ makeinc's "n" from inside def incr, there's no way to ask for its
value (inside def incr).

> (3) Or some builtin functions sort of like my bind and merge
> that explicitly manipulate the functions environment.

Agree these would add some intriguing capabilities to the language, and I
hope you pursue the ideas. But wrt making the current lambda "work",
it's gotta be overkill (sufficient, but overwhelmingly so).

and-i'd-mention-that-dynamic-scoping-would-solve-the-problem-except-
guido-would-break-my-kneecaps<ouch!>-ly y'rs - tim

Tim Peters tim@ksr.com
not speaking for Kendall Square Research Corp