Q: Python 2.0 preliminary features?

2 views
Skip to first unread message

Preston Landers

unread,
Oct 8, 1999, 3:00:00 AM10/8/99
to
Hello all,

I know there's been some casual discussion here about Python 2.x. I'm
wondering if anyone would be willing to summarize a list of language
features generally desired or expected for Python 2. I'm not really
asking about the implementation but the language itself; any
interesting changes ahead?

And just because I like to start flame wars, I'll go ahead and add my
own feature to the wishlist: nested scopes / namespaces. Is this a
pipe dream, or just too hard to implement given the internal structure
of Python (which I'm not really familiar with.) Maybe I'm just going
about it all wrong, but it seems like much of my code would be cleaner
given arbitrarily nested scopes. I've discussed this with people
before on c.l.python but never really got an explanation that made
sense (to me) for the way it is now.

By the way, is there a function equivilent to the following in the
standard Python module library?

def unquote(s):
"""Remove any enclosing quotes around a string."""
if (s[:1] == "'" and s[-1:] == "'") or (s[:1] == '"' and s[-1:]
== '"'):
return s[1:-1]
else:
return s

thanks,

--
|| Preston Landers <preston...@my-deja.com> ||


Sent via Deja.com http://www.deja.com/
Before you buy.

Eric Jacobs

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to
Preston Landers wrote:
>
> And just because I like to start flame wars, I'll go ahead and add my
> own feature to the wishlist: nested scopes / namespaces. Is this a
> pipe dream, or just too hard to implement given the internal structure
> of Python (which I'm not really familiar with.) Maybe I'm just going
> about it all wrong, but it seems like much of my code would be cleaner
> given arbitrarily nested scopes. I've discussed this with people
> before on c.l.python but never really got an explanation that made
> sense (to me) for the way it is now.

I too am for rethinking of the way that scopes are handled in Python.
But I'm not too sure what you mean by "arbitrarily nested" scopes.
Python is a bit different from other languages with scope rules
for at least two reasons: that there is no need to predeclare any
variables, and that functions are fully first-class.

Consider this function:

def f(y):
f1 = lambda y=y: y.foo
f2 = lambda foo=y.foo: foo
return (f1, f2)

Here the two functions that are returned do completely different
things. The first retrieves the current value of foo for the
object that was originally passed. The second always returns
a constant, whatever value that foo had when the function was
called. If we could simply write:

f3 = lambda : y.foo

the automatic scope nester would have to decide what you meant.
Kind of a stupid example, because usually you would want the
function that looked up the current value, although I actually
have had occasions where I needed the constant bird, and I was
kind of glad that Python made you spell it out.

Traditionally, scope would be resolved by chaining the local
scope (inside the lambda) to the next outer scope (f()'s
locals). Python already does something similar to this, in the
way that an object chains from its own dir() to its class.
But that would be very tricky to do for functions, because
sub-functions are first class and can exist even when their
containing function has exited. If the sub-function chained
to the containing function's namespace, that would mean that
the whole stack frame would essentially have to be moved
to a "heap frame" and ref-counted and handled like an
independent object!

That seems a bit extreme. But anything short of that, and you
still have that barrier to cross (going from the containing
function's namespace to where the subfunction can pick it
up.) Being able to specify how that translation takes place
is very important for defining functions that can be used
somewhere other than where they were created (a very useful,
very cool feature of Python IMO.)

Alexander Williams

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to
Note that Scheme already does what many consider 'the right thing'
(myself, among others), and that Scheme is a language in which
functions are first-class objects, possess closures (lambdas), and has
arbitrarily deep lexical scoping (of the kind I wish Python
possessed).

Note that you can do away with the 'global' statement altogether in
Python-With-Lexical-Scope/Closure since you can create an empty object
named 'Global' in the top-level scope which will then be accessible in
all others (as long as you're smart enough to not shadow it).

[Ahhhh, to have Scheme's lexical scope and Haskell's lazy
evaluation/list comprehensions/type declarations/type inferences in
Python syntax. Pure bliss.]

--
Alexander Williams (tha...@gw.total-web.net)
"In the end ... Oblivion Always Wins."

Tim Peters

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to pytho...@python.org
[Eric Jacobs, cogitating about nested functions & lexical scopes]
> ...

> But that would be very tricky to do for functions, because
> sub-functions are first class and can exist even when their
> containing function has exited. If the sub-function chained
> to the containing function's namespace, that would mean that
> the whole stack frame would essentially have to be moved
> to a "heap frame" and ref-counted and handled like an
> independent object!

Heh heh. Python already does all of that (frames are heap-allocated
already, and are refcounted like everything else):

>>> try:
... raise "error"
... except:
... import sys
... print sys.exc_info()[2].tb_frame
>>>
<frame object at 80c5b0>
>>>

The only technical hangup in implementing lexical closures is that it
creates cycles among internal objects, which refcounting can't reclaim.
Greg Ewing recently worked up a patch that implements "read only" lexical
closures, that avoids the cycles in many (most, or almost all, for most, or
almost all <wink>, common programming styles) cases, albeit with some other
costs (see DejaNews).

It may (or may not) be revealing that people who have used Python since The
Beginning don't agitate for this. Python's current "two-level" scoping has
charms of it own; abuse of the default argument mechanism to simulate
closures by hand makes it very explicit which names leak across the scope
boundary; and creating a class is often a much better way to manage a
namespace with indefinite extent. I expect the pressure for lexical
closures will prove too intense to withstand, but even as a retired
functional-language fan I don't think they'd really add anything to
Python -- apart from the opportunity to pretend you're programming in some
other language <0.9 wink>.

flat-is-better-than-nested-ly y'rs - tim

Neel Krishnaswami

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to
On Fri, 08 Oct 1999 20:29:30 GMT, Preston Landers
<preston...@my-deja.com> wrote:
>
>I'm not really asking about the implementation but the language
>itself; any interesting changes ahead?

Yes, Python 2.0 will be Perfect(tm). :) Other than that, only Guido
knows for sure.

Some of the more common requests are:

o Elimination of the class/C-type dichotomy. C extension types (like
dictionaries and lists) should be real Python classes.

o Lexical scoping, a la Scheme.

o Optional type anotations, in order to enable more compile-time type
checking, and to (possibly) allow faster native Python code. (I'm
dubious about the latter working, though.)

o A better-designed iteration protocol.

There are some other features that are less frequently requested but
may happen simply because there are people actually willing to sit
down and implement them. (These might even make 1.6, for all I know.)

o List comprehension syntax, a la Haskell. In fact, Greg Ewing has
already implemented this as a patch.

o First-class continuations, a la Scheme. This might make it in
just to permit coroutines and exceptions with restarts. Look at
Christian Tismer's Stackless Python for the current state.

>And just because I like to start flame wars, I'll go ahead and add my
>own feature to the wishlist: nested scopes / namespaces. Is this a
>pipe dream, or just too hard to implement given the internal structure
>of Python (which I'm not really familiar with.) Maybe I'm just going
>about it all wrong, but it seems like much of my code would be cleaner
>given arbitrarily nested scopes. I've discussed this with people
>before on c.l.python but never really got an explanation that made
>sense (to me) for the way it is now.

The basic reason that Python does not currently support lexical
scoping is because Python uses a reference-counting garbage-collection
scheme, which can't eliminate cyclic trash, and lexical closures tend
to create a lot of it.

Since the reference-counting scheme is woven fairly deeply into
Python, this is why lexically nested namespaces won't be seen in any
Python 1.x, and why people keep pushing garbage-collection is a
priority for Python 2.0.

If you are desperate for it now, you should look at Neil Schemenauer's
patch to get the Python interpreter to use the Boehm garbage collector,
and add in Greg Ewing's lexical scoping patch. That should get you 75%
of what you want. (The remaining 25% requires some syntactic changes to
Python, in order to allow nested local namespaces to shadow properly.
Talk to John Max Skaller about Viper to see what his sense of the
problem and what the potential solutions are.)


Neel

Neel Krishnaswami

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to
On Sat, 09 Oct 1999 07:00:27 GMT, Alexander Williams
<tha...@brimstone.mecha> wrote:
>
>Note that you can do away with the 'global' statement altogether in
>Python-With-Lexical-Scope/Closure since you can create an empty object
>named 'Global' in the top-level scope which will then be accessible in
>all others (as long as you're smart enough to not shadow it).

Actually, you can't. You still need a keyword of some kind to
explain what is and isn't shadowed.

The problem is that in Python variables are both created with and
modified by an assignment statement. This means that it's not
possible to unambiguously tell from the syntax whether you want
to create a new variable in the current scope or modify a variable
in an enclosing scope. IOW, what should the following code do?

def puzzle(s):
def inner():
s = "Nee"
inner()
return s

What should puzzle("Shrubbery") return? "Shrubbery" or "Nee"? Right now
the ``s = "Nee"'' statement creates a new variable binding as well as
performs an assignment, so puzzle("Shrubbery") returns "Shrubbery".

Scheme and Dylan avoid this problem by separating declaration and
assignment into the let and set! (:= for Dylan) forms. Thus the
spelling of the two versions is unambiguous:

(define (puzzle-1 s)
(let ((inner (lambda ()
(let ((s "Nee")) ()))))
(inner)
s))

(define (puzzle-2 s)
(let ((inner (lambda ()
(set! s "Nee"))))
(inner)
s))

If we wanted to keep the current overloading of "=", Python would need
a "nonlocal" keyword to disambiguate. (This is kind of the backwards
of the Scheme solution.) So the Python tranlation of puzzle-2 would
be this:

def puzzle_2(s):
def inner():
nonlocal s # We want use the s from an enclosing scope
s = "Nee"
return s
inner()
return s

IMO it's not quite as elegant as the Scheme version, but the difference
is small enough to be a matter of taste.


Neel

Alexander Williams

unread,
Oct 9, 1999, 3:00:00 AM10/9/99
to
On 9 Oct 1999 22:35:29 GMT, Neel Krishnaswami <ne...@brick.cswv.com> wrote:
>IMO it's not quite as elegant as the Scheme version, but the difference
>is small enough to be a matter of taste.

I could accept this solution (though I'd rather spell 'nonlocal' as
'=:' as Dylan does, as it certainly makes sense in context).

David Oppenheimer

unread,
Oct 10, 1999, 3:00:00 AM10/10/99
to

Alexander Williams wrote:

> On 9 Oct 1999 22:35:29 GMT, Neel Krishnaswami <ne...@brick.cswv.com> wrote:
> >IMO it's not quite as elegant as the Scheme version, but the difference
> >is small enough to be a matter of taste.
>
> I could accept this solution (though I'd rather spell 'nonlocal' as
> '=:' as Dylan does, as it certainly makes sense in context).

Who is Dylan?

Fredrik Lundh

unread,
Oct 10, 1999, 3:00:00 AM10/10/99
to David Oppenheimer
David Oppenheimer <davi...@megsinet.net> wrote:
> > I could accept this solution (though I'd rather spell 'nonlocal' as
> > '=:' as Dylan does, as it certainly makes sense in context).
>
> Who is Dylan?

www.dylan.org (python-food, yum, yum)

or perhaps they mean the guy who runs
comp.lang.dylan. more info here:
http://www.functionalobjects.com/resources.html

</F>


Andrew Dalke

unread,
Oct 10, 1999, 3:00:00 AM10/10/99
to
David Oppenheimer <davi...@megsinet.net> asked:
> Who is Dylan?

From Simon and Garfunkle's "Simple Desultury Phillipic",
(see http://www.paulsimon.org/noframe.html)

| He's so unhip that
| When you say Dylan, he thinks you're talking about Dylan Thomas,
| Whoever he was.
| The man ain't got no culture,

Your off-tangent reference for the day :)

Andrew Dalke
da...@acm.org

Greg Ewing

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
Tim Peters wrote:
>
> Greg Ewing recently worked up a patch that implements "read only" lexical
> closures, that avoids the cycles in many (most, or almost all, for most, or
> almost all <wink>, common programming styles) cases, albeit with some other
> costs (see DejaNews).

I'd just like to point out that my closures are only "read only"
because I have deferred making a syntactic decision about how to declare
variables as "nonlocal" for assignment purposes; there's no inherent
reason why my closures couldn't be read-write.

Also, to clarify Tim's rather waffly characterisation of when my
closures do or don't cause cycles, it's quite simple: they never
cause cycles unless you write code which makes them do so. This
is no worse than the situation with regard to cycles everywhere
else in the language.

Caveat-generator-cycloramum,
Greg

D'Arcy J.M. Cain

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
Neel Krishnaswami <ne...@brick.cswv.com> wrote:
> Yes, Python 2.0 will be Perfect(tm). :) Other than that, only Guido
> knows for sure.

> Some of the more common requests are:

Here is an uncommon one. I have already presented this to Guido but
he doesn't feel it should be added. Perhaps a few more voices will
convince him to rethink it. I know he isn't _violently_ opposed
because he originally added an earlier version and then backed it out.

crypt takes two parameters, the string to encrypt and the salt. My
proposal is simply to make the second argument optional in which case
it generates a random salt.

Originally the mods I sent were flawed in that it seeded the RNG on init
of the module. This was bad because it introduced a change to programs
that did not expect it. That was the version that Guido backed out and
rightly so.

The corrected version I sent only seeds the RNG the first time it is
called without the second parameter. That way the only time it happens
is if the programmer specifically expects it to happen.

I am also open to reasons why this is a bad idea. I currently modify
cryptmodule.c whenever I install Python and have never seen a problem.
I use encryption often enough that not having to do extra work before
the call (or having to use a wrapper function) makes this worthwhile.

What do you think, sirs?

--
D'Arcy J.M. Cain <da...@caingang.com> | Democracy is three wolves
http://www.druid.net/darcy/ | and a sheep voting on
+1 416 425 1212 (DoD#0082) (eNTP) | what's for dinner.

skaller

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
Preston Landers wrote:

> And just because I like to start flame wars, I'll go ahead and add my
> own feature to the wishlist: nested scopes / namespaces. Is this a
> pipe dream

This is already implemented in Viperi. Works fine: there are
no problems with representation because it is using a functional
programming language with a garbage collector as the implementation
language. However, I do not use chained stack frames, and I do not
use Python's 'two scope with builtins hack' model; instead,
there is a single abstraction called an environment in which
lookup is done; the implementation uses a list of dictionaries,
amoung other things, to represent the 'display' (list of activation
records). [The 'globals' scope is near the bottom, builtins is at the
bottom]

In the second stage, fast loading is supported, using an indexed
array instead of a dictionary: CPython does this for functions,
whereas Viper does it for both nested functions and modules as well.
[I'm currently implementing this]. Inlining naturally fits into
this stage, in the first instance.

In the third stage, type inference will attempt to ascribe types to
the variables, and use some fixed ad hoc speedups to improve
interpreter speed even further.

Finally, the fourth stage converts client functions to built-in
functions, that is, compiled code (probably, machine code);
and binds the program down to an binary executable.

[It isn't clear yet whether to generate for the CPython object
model or not, but this has advantages such as performance
and compatibility that are hard to overlook, but will make
extensions like nested functions harder to support]

--
John Skaller, mailto:ska...@maxtal.com.au
1/10 Toxteth Rd Glebe NSW 2037 Australia
homepage: http://www.maxtal.com.au/~skaller
downloads: http://www.triode.net.au/~skaller

skaller

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
Greg Ewing wrote:

> I'd just like to point out that my closures are only "read only"
> because I have deferred making a syntactic decision about how to declare
> variables as "nonlocal" for assignment purposes; there's no inherent
> reason why my closures couldn't be read-write.

You do not need to make any decision. Just use the current 'global'
declaration, this is what Viperi does. The trick is to understand that
'global' means 'up one level exactly', as it does in python now (which
only has two levels).

If you want to write up TWO levels of scope, then the parent
must also contain a global directive:

def f():
x = 0
def g():
global x
def h():
global x
x = 1


This seems consistent. It is not entirely python compatible,
since the global directive in the function h() would normally
refer to module scope. But that is probably irrelevant,
since lexically scoped nested functions aren't compatible with
python ones anyhow.

If you wish to minise syntactic changes to Python, you might
consider this solution, it will also be compatible with
Viper. :-)

Neel Krishnaswami

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
On Mon, 11 Oct 1999 12:16:15 +1000, skaller <ska...@maxtal.com.au> wrote:
>
>In the third stage, type inference will attempt to ascribe types to
>the variables, and use some fixed ad hoc speedups to improve
>interpreter speed even further.

How do you plan to mix type inference and extension-type/Python-class
unification?

As I understand it, type inference in OO languages is generally only a
win if you can do enough to infer the actual runtime type of the
object. IOW, it's not enough to know that the object is an instance of
some subclass of the class Number, but that it's a direct instance of
the class Integer. Then you can avoid paying the cost of polymorphic
dispatch, and can take advantage of knowing the actual method called
to do additional optimizations (like inlining).

But if Python's primitive types are made into real Python classes,
then any type can be potentially be subclassed, which makes inferring
the runtime type a much harder prospect.

AFAICT, you need to either add something like Java's "final" keyword,
do whole-program analysis (does eval defeat this?), or do dynamic
compilation like Self. Or are there some cool new tricks you can steal
for Viper from the OCaml compiler..?


Neel

Neel Krishnaswami

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to
On Sun, 10 Oct 1999 01:45:12 -0400, David Oppenheimer <davi...@megsinet.net>
wrote:

>Alexander Williams wrote:
>>
>> I could accept this solution (though I'd rather spell 'nonlocal' as
>> '=:' as Dylan does, as it certainly makes sense in context).
>
>Who is Dylan?

Dylan (portmanteu of DYnamic LANguage) is a very slick language that
attempts to keep as many powerful Lisp-like features as possible while
still allowing separate compilation and fast binaries. Its semantics
are somewhat like Scheme plus the Common Lisp Object System, though
IMO it's even cleaner than either of those two alone.

You can find a free compiler at http://www.gwydiondylan.org. It's well
worth looking at because a lot of the features being mooted for Python
2.0 have already been implemented in Dylan, and it can give a good
sense of how other people approached the same problems.


Neel

Jeremy Hylton

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to skaller
I don't understand why you need the global declaration in g.

def f():
x = 0
def g():
global x
def h():
global x
x = 1

At compile time, you should know which scopes have local variables
named x and which have global declarations for x. If g does not have
a local variable named x, then the global declaration in h can't
sensibly mean the local variable x in g's scope.

The requirement that there be a global declaration in every
intervening scope seems unnecessary and error-prone. If you miss one
global statement, you get either weird behavior or exceptions.

This isn't exactly compatible with the current semantics for global,
but it would only cause problems for really obscure code, like this:

x = 12
def spam():
x = 1
def eggs():
global x
y = x - 2

Under current global semantics, the x in eggs would refer to the
global variable x with the value 12. Under the new semantics, the x
in eggs would refer to the local variable x in spam with the value 1.
So it would break some code, but not very much; only code that uses
multiple nested functions that use the same name for local and global
variables.

Jeremy

Jeremy Hylton

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to Greg Ewing
>>>>> "GE" == Greg Ewing <greg....@compaq.com> writes:

GE> Also, to clarify Tim's rather waffly characterisation of when my
GE> closures do or don't cause cycles, it's quite simple: they never
GE> cause cycles unless you write code which makes them do so. This
GE> is no worse than the situation with regard to cycles everywhere
GE> else in the language.

I think this description is at least as waffly as Tim's :-). It seems
to me that most of the interesting uses of lexical scoping necessarily
create cycles. Is there a large class of uses that will not create
cycles?

One of the prototypical uses of lexical scoping is functions like
these:

def make_adder(num):
def adder(x):
global num
return x + num
return adder

In this case, adder maintains a reference to make_adder's
environment, and that environment maintains a reference to adder,
because adder was defined there. So we have a cycle...

It seems odd to propose adding lexical scoping to Python, and then
tell people not to write code like make_adder.

Jeremy

Tim Peters

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to pytho...@python.org
[Greg Ewing]

> Also, to clarify Tim's rather waffly characterisation of when my
> closures do or don't cause cycles, it's quite simple: they never
> cause cycles unless you write code which makes them do so.

[Jeremy Hylton]


> I think this description is at least as waffly as Tim's :-).

I went into some detail on when Greg's patch would and wouldn't create
cycles in an earlier (but still very recent) msg, and didn't feel like
repeating it all. That was certainly less waffleworthy than Greg's
content-free tautology above <wink>.

> It seems to me that most of the interesting uses of lexical scoping
> necessarily create cycles. Is there a large class of uses that will
> not create cycles?

In Greg's approach, yes -- although at a cost.

> One of the prototypical uses of lexical scoping is functions like
> these:
>
> def make_adder(num):
> def adder(x):
> global num
> return x + num
> return adder
>
> In this case, adder maintains a reference to make_adder's
> environment, and that environment maintains a reference to adder,
> because adder was defined there. So we have a cycle...

In Greg's scheme (which you may be confusing with John Skaller's), you write
that in the more Scheme-like way:

def make_adder(num):
def adder(x):


return x + num
return adder

That is, Greg doesn't change the meaning of Python's "global", and you don't
want "global" here. Non-local references are searched for up a chain of
static (lexical) links, in the usual way.

The "def adder" does *not* create a closure (and hence neither a cycle)
under Greg's scheme. It binds a new kind of object to "adder", a trivial
wrapper around the function object currently stored. No info about bindings
is contained in this new object. The sole purpose of the new object is to
force a *later* operation to create a closure.

The magic all happens in LOAD_FAST, which is (for speed), alas, the worst
possible opcode to pick on. When LOAD_FAST retrieves an object of this new
type, *then* it creates a closure, out of the function wrapped by the new
object and the current frame. The beauty of it is that this closure (the
object returned by "return") is anonymous: it refers to make_adder's
locals, but make_adder's locals do not refer to *it*. make_adder's locals
refer only to the new wrapper object bound to "adder", which in turn refers
only to the (raw) function object.

So there is no cycle here, and everything works fine. It's very clever.
It's also hard to know when you're creating a cycle, unless you're Greg <0.1
wink>; e.g., this variant does create a cycle:

def make_adder(num):
adder = lambda x: x + num # lambda is now very different from def
return adder

and so does:

def make_adder(num):
def adder(x):
return x + num
def suber(x):
return x - num
methods = [adder, suber]
return methods

while:

def make_adder(num):
return lambda x: x + num

does not; and so on (see my earlier msg).

no-msg-really-gets-sent-until-it's-typed-twice<wink>-ly y'rs - tim

Jeremy Hylton

unread,
Oct 11, 1999, 3:00:00 AM10/11/99
to Tim Peters
[I wrote:]

>> def make_adder(num):
>> def adder(x):
>> global num
>> return x + num
>> return adder
>> In this case, adder maintains a reference to make_adder's
>> environment, and that environment maintains a reference to adder,
>> because adder was defined there. So we have a cycle...

>>>>> "TP" == Tim Peters <tim...@email.msn.com> writes:

TP> In Greg's scheme (which you may be confusing with John
TP> Skaller's), you write that in the more Scheme-like way:

(Yes. I'm definitely getting my Skallers and Ewings confused. Sorry.)

TP> def make_adder(num):
TP> def adder(x):
TP> return x + num
TP> return adder

TP> The "def adder" does *not* create a closure (and hence neither a
TP> cycle) under Greg's scheme. It binds a new kind of object to
TP> "adder", a trivial wrapper around the function object currently
TP> stored. No info about bindings is contained in this new object.
TP> The sole purpose of the new object is to force a *later*
TP> operation to create a closure.

Ok. I think I've got it now. These are the read-only variant of
closures, right? So the following will not work?

def make_accumulator():
sum = 0
def accumulate(x):
sum = sum + x
return sum
return accumulate

Of course, there isn't much lost here, because I can spell this in
Python a lot more easily by starting with "class."

TP> I went into some detail on when Greg's patch would and wouldn't
TP> create cycles in an earlier (but still very recent) msg, and
TP> didn't feel like repeating it all.

now-on-to-reading-the-earlier-messages-later-ly y'rs,
Jeremy

Greg Ewing

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
skaller wrote:
>
> You do not need to make any decision. Just use the current 'global'
> declaration, this is what Viperi does.

That's a decision in itself, since it extends the
meaning of "global" in one of at least two different
ways.

My purpose in implementing the patch was to demonstrate
how lexical scoping could be done without introducing
intolerable memory management problems. If Guido ever
decides to adopt it, I'm happy to leave it up to him
to pick one of the several possible nonlocal declaration
syntaxes.

If I get another burst of enthusiasm I might implement
one of them myself. My personal inclination is to require
declaration of all variables, but I get the feeling that
would be just a bit too radical for many Pythoneers to
swallow...

Greg

Greg Ewing

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
Jeremy Hylton wrote:
>
> I think this description is at least as waffly as Tim's :-).

On reflection, you're quite right -- some knowledge of how my
patch works is needed in order to inerpret it properly, so
some elaboration is in order.

> def make_adder(num):
> def adder(x):
> global num
> return x + num
> return adder
>
> In this case, adder maintains a reference to make_adder's
> environment, and that environment maintains a reference to adder,
> because adder was defined there. So we have a cycle...

Under my scheme, that piece of code in itself does not create
a cycle. This is because a nested def does not immediately create
a closure; instead it creates a special "local function object"
which simply wraps the function. A closure is created "on
demand" whenever a local function object is fetched out of a
local variable. This is analogous to the way a bound method
object is created whenever a function is fetched out of a
class during an attribute lookup.

Whether a cycle gets created or not depends on what you do with
the closure once you've got it. Many common uses will not
create any cycles, e.g.

def add_to_each(items, x):
def adder(y):
return x+y
return map(adder, items)

or any other use which only involves passing closures "inwards".

There are other common uses that will indeed create cycles.
I'm not saying that you shouldn't do those things -- only
that you need to be aware of the cycles you're creating and
take steps to break them later. Just like you have to do with
any other cyclic data structure in Python.

Many of these uses would have created cycles anyway if done
using one of the alternatives to closures. For example,
consider an object with an attached UI widget which makes
a callback to a method of the object:

class Furble:

def __init__(self, ...):
...
self.button = Button(...,
command=lambda self=self: self.wibble(42))

This creates a cycle, because the instance has a reference
to the button whose callback has a default argument referring to
the instance. This isn't a problem in practice because usually
you will destroy() the instance's attached widgets when
you're finished with it.

Since we're doomed to have a cycle anyway, we might as
well use a lexical closure and do away with the
ugly default argument hack:

self.button = Button(...,
command=lambda: self.wibble(42))

Hope that makes things less waffly,
Greg

Phil Hunt

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
In article <slrn7vvh66...@brick.cswv.com>

ne...@alum.mit.edu "Neel Krishnaswami" writes:
> Yes, Python 2.0 will be Perfect(tm). :) Other than that, only Guido
> knows for sure.
>
> Some of the more common requests are:
>
> o Elimination of the class/C-type dichotomy. C extension types (like
> dictionaries and lists) should be real Python classes.

I'd like this too.

> o Lexical scoping, a la Scheme.

What is this?

Another feature I would like is multi-line comments. Consider a
paragraph of commented text under the present system; every time
you want to add a few new lines to the comment you have to
manually put in `#'s at the start of each new comment line, which I
find irritating because it breaks the flow of what I am trying to
explain.

/*****
Multi-line comments, like the one this paragraph is surrounded in,
are better, becasue there is no comment character at the sart of
every new line. So I can write more text, adding to my comment,
without this annoyance.
*****/

Some people might argue that it is only a minor annoyance. But I
would reply that well-written, explanative comments are a good
thing, and that anything that encourages them, or makes them
easier to write, is also good.

--
Phil Hunt - - - - - - - - - ph...@vision25.demon.co.uk
- Linux was 8 years old on 17th September! See: -
http://www.vision25.demon.co.uk/prog/linuxbirthday.html


Jeremy Hylton

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to skaller
>>>>> "MS" == skaller <ska...@maxtal.com.au> writes:

[Earlier, I wrote:]


>> def f():
>> x = 0
>> def g():
>> global x
>> def h():
>> global x
>> x = 1
>>
>> At compile time, you should know which scopes have local
>> variables named x and which have global declarations for x. If g
>> does not have a local variable named x, then the global
>> declaration in h can't sensibly mean the local variable x in g's
>> scope.
>>

MS> Sure it can!! This is standard Python:

MS> def q():
MS> global x
MS> x = 1

MS> Here, there is no 'global' variable x, it is in fact created by
MS> executing q. There is no way to 'know' which scope a nested
MS> function's 'global' statement refers to without a convention.

The top-level scope is a special case. At compile-time, you should
also know whether the function is defined at the top-level or is
nested within another function.

MS> The Python convention is 'module scope, always', which also
MS> applies to all non-local variables on reading.

I'm suggesting a different convention -- nearest enclosing scope or
top-level if no enclosing scope creates a binding.

MS> However, there is a new factor here I overlooked: a function can
MS> gave a local variable that is NOT visible in the function, not
MS> even in an 'exec': one which is local because it is declared
MS> global in a function nested in it.

Hadn't thought about that particular wrinkle, but I'd be inclined to
include it in part of the top-level special case. You could outlaw
the creation of new bindings from enclosed function definitions and
require that a binding be created explicitly -- top-level excluded.
I suppose that isn't terribly Pythonic...

but-neither-is-global-decls-in-every-function-ly yr's,
Jeremy

Eric Jacobs

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
Jeremy Hylton wrote:
>
> Ok. I think I've got it now. These are the read-only variant of
> closures, right? So the following will not work?
>
> def make_accumulator():
> sum = 0
> def accumulate(x):
> sum = sum + x
> return sum
> return accumulate

Right. It won't do what you'd expect it to do. This is a problem
with the syntax of Python, rather than any particular method of
implementing closures. The problem is that because Python doesn't
require variables to declared before they are used, the statement
"sum = sum + x" doesn't specify in itself whether a new "sum"
variable is to be created in the innermost scope, or the "sum"
that's in the make_accumulator scope is to be modified. (Or,
for that matter, any scope beyond that.)

This "read-only-ness" really isn't specific to lexical scoping.
It's just the way Python has traditionally done business
whenever multiple namespaces are tied together. It's the
same sort of thing as in standard Python:

class c:
x = 5

and if I give you an instance of c, you can read my 5 (from
the class namespace) but not modify it (because you always
modify your instance's namespace.) Hence, read-only.

Guido van Rossum

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
(I'm falling into the middle again. But this is interesting. I hope
I haven't misunderstood Greg's description.)

Greg Ewing <greg....@compaq.com> writes:
> Under my scheme, that piece of code in itself does not create
> a cycle. This is because a nested def does not immediately create
> a closure; instead it creates a special "local function object"
> which simply wraps the function. A closure is created "on
> demand" whenever a local function object is fetched out of a
> local variable. This is analogous to the way a bound method
> object is created whenever a function is fetched out of a
> class during an attribute lookup.

Hm. I don't find it analogous, and while I agree that it's a clever
hack to avoid cycles, I don't like it much. Here's my reason:

I dislike defining object types with "magic" properties that are
effectuated whenever they are loaded. The magic may easily be
triggered at the wrong moment -- e.g. the debugger may be looking at
your stack frame, and depending on whether it copies the object it
pulls out of your stack frame into a local variable before looking at
it or not, it will see either a function or the magic object. (It
doesn't have to be the debugger -- it could be any code looking at
locals()).

This is different from the method lookup -- that is only done in a
very specific context (when during attribute lookup on an instance the
object is found in the class).

Note that this is also one of the reasons why I'm feeling uneasy with
the stackless Python patch set (another reason is that it can't be
done in JPython so it can't be made a part of the language standard):
as far as I recall, it uses a magic object which when returned signals
the interpreter to execute some callback that the called function
wanted to be executed (this is necessary for apply() I believe).

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

Guido van Rossum

unread,
Oct 12, 1999, 3:00:00 AM10/12/99
to
Regarding the use of 'global' in combination with nested scopes:
I have always been in favor or *not* providing a way to assign to
variables at the intermediate levels. That is, you can *use*
variables from intermediate levels, but you can't *set* them. The
'global' statement will set the variable at the module-global level.

Motivation: I agree that it is useful to reference variables in the
surrounding scope. But if you start modifying them, you're abusing
the nested scope mechanism to hold mutable state; it's much clearer
to define a class for this purpose.

In particular, I think that allowing

def f():


sum = 0
def accumulate(x):

global sum
sum = sum+x
...code using accumulate()...
return sum

tends to create obfuscated code.

skaller

unread,
Oct 13, 1999, 3:00:00 AM10/13/99
to jer...@cnri.reston.va.us
Jeremy Hylton wrote:
>
> I don't understand why you need the global declaration in g.
>
> def f():
> x = 0
> def g():
> global x
> def h():
> global x
> x = 1
>
> At compile time, you should know which scopes have local variables
> named x and which have global declarations for x. If g does not have
> a local variable named x, then the global declaration in h can't
> sensibly mean the local variable x in g's scope.
>
Sure it can!! This is standard Python:

def q():


global x
x = 1

Here, there is no 'global' variable x, it is in fact created
by executing q. There is no way to 'know' which scope a nested


function's 'global' statement refers to without a convention.

The Python convention is 'module scope, always', which also

applies to all non-local variables on reading.

However, there is a new factor here I overlooked: a function
can gave a local variable that is NOT visible in the function,
not even in an 'exec': one which is local because it is declared


global in a function nested in it.

> The requirement that there be a global declaration in every


> intervening scope seems unnecessary and error-prone. If you miss one
> global statement, you get either weird behavior or exceptions.

You are right it may be error prone, but _something_ is necesary.



> This isn't exactly compatible with the current semantics for global,
> but it would only cause problems for really obscure code, like this:
>
> x = 12
> def spam():
> x = 1
> def eggs():
> global x
> y = x - 2
>
> Under current global semantics, the x in eggs would refer to the
> global variable x with the value 12. Under the new semantics, the x
> in eggs would refer to the local variable x in spam with the value 1.
> So it would break some code, but not very much; only code that uses
> multiple nested functions that use the same name for local and global
> variables.

Your rule would only work if the variable was, in fact, created by the
enclosing function before it was used in the enclosed function.
This is contrary to the current rule, where the enclosing function
is taken as module scope.

John Farrell

unread,
Oct 13, 1999, 3:00:00 AM10/13/99
to ph...@vision25.demon.co.uk
Phil Hunt wrote:
> Another feature I would like is multi-line comments. Consider a
> paragraph of commented text under the present system; every time
> you want to add a few new lines to the comment you have to
> manually put in `#'s at the start of each new comment line, which I
> find irritating because it breaks the flow of what I am trying to
> explain.

Call me a newbie but what's wrong with inserting a string constant in
the middle of the code?

import dis

def test(x):
x = x * x
"""Now take one off.

We do this by using the subtract operator.
"""
x = x - 1
return x

print test(7)
print dis.dis(test)

If I comment out the string, I get one less byte code which is a
(redundant) SET_LINENO. If the compiler folded sequential SET_LINENOs,
the string would act just like a comment at run-time.

John

David Fenyes

unread,
Oct 14, 1999, 3:00:00 AM10/14/99
to

Phil> Another feature I would like is multi-line
Phil> comments. Consider a paragraph of commented text under the
Phil> present system; every time you want to add a few new lines
Phil> to the comment you have to manually put in `#'s at the start
Phil> of each new comment line, which I find irritating because it
Phil> breaks the flow of what I am trying to explain.

Any good editor (such as emacs) should automatically wrap the line
you're typing, add a new comment marker, and indent the text to the
proper place. The problem with multi-line comments is that they don't
nest well, amplifying the annoyance of commenting/uncommenting a group
of lines.


--
David Fenyes -- dfe...@home.com

Aahz Maruch

unread,
Oct 14, 1999, 3:00:00 AM10/14/99
to
In article <m3yad8u...@c34680-a.grlnd1.tx.home.com>,

David Fenyes <dfe...@flash.net> wrote:
>
>Any good editor (such as emacs) should automatically wrap the line
>you're typing, add a new comment marker, and indent the text to the
>proper place. The problem with multi-line comments is that they don't
>nest well, amplifying the annoyance of commenting/uncommenting a group
>of lines.

Comments can be defined to nest.
--
--- Aahz (@netcom.com)

Androgynous poly kinky vanilla queer het <*> http://www.rahul.net/aahz/
Hugs and backrubs -- I break Rule 6 (if you want to know, do some research)

Greg Ewing

unread,
Oct 14, 1999, 3:00:00 AM10/14/99
to
Guido van Rossum wrote:

> def f():
> sum = 0
> def accumulate(x):
> global sum
> sum = sum+x
> ...code using accumulate()...
> return sum
>
> tends to create obfuscated code.

I can't see anything very obfuscated about that -- the
two uses of sum are only two lines apart, after all.

It could become obfuscated if the code were much more
complicated. But it's the very simple cases like that --
where the overhead of defining and instantiating a
class would totally swamp the code being wrapped --
that one feels the lack of lexical scoping most keenly.

In any case, is that really any worse than

def f():
sum = [0]
def accumulate(x):
global sum

sum[0] = sum[0] + x
...code using accumulate()...
return sum[0]

which is the sort of thing people are going to do if
you restrict lexical scoping to being read-only. To my
way of thinking, the original version is clearer.

I suspect that, if you introduce read-only lexical scoping,
you're just going to substitute the stream of "Why is there
no #@%$#$% lexical scoping in Python" complaints with "Why
the #@%$#$% can't I assign to a non-local variable in Python"...

Greg

Greg Ewing

unread,
Oct 14, 1999, 3:00:00 AM10/14/99
to
Guido van Rossum wrote:
>
> The magic may easily be
> triggered at the wrong moment -- e.g. the debugger may be looking at
> your stack frame, and depending on whether it copies the object it
> pulls out of your stack frame into a local variable before looking at
> it or not, it will see either a function or the magic object.

You have a point there. This shows up a deficiency in my
implementation that I hadn't noticed before.

The *intention* was that the local_function -> closure
transformation should only occur in one context, that is,
when the local variable is referred to by name in the
function or a function defined within it.

To implement this properly, the compiler should keep track
in its symbol table of which local variables were created
by def statements, and emit a closure-creating opcode after
each operation which loads one of them.

This would require some major reorganisations of the
compiler, since the symbol table would have to encompass
the lexically enclosing environments of the function being
compiled, not just its own locals.

If such a reorganisation were undertaken, it would have
other benefits as well. It would completely solve the
speed problem, since loading of non-functions would not
be affected at all. It would also enable access to
intermediate-level variables in constant time, since the
compiler would know at which lexical level each variable
resides.

Incidentally, there is an analogous problem with the
function->method transformation: it is impossible to have
a class variable which holds a function. If you assign
a function to a class attribute and read it back again,
you get either an unbound_method or a bound_method (depending
on whether you fetch it through the class or an instance).
This piece of "magic" frequently trips people up.

One final note: I never intended that the existence of
local_function objects should be hidden from users. If
implemented, the mechanism would have to be clearly
explained in the manual, just like tbe bound_method
mechanism is now.

One final final note: I agree that this is a hack which
should not be necessary. If you're ever trying to choose
between implementing garbage collection or lexical closures
first, please go for garbage collection!

> Note that this is also one of the reasons why I'm feeling uneasy with

> the stackless Python patch set ... it uses a magic object

I wouldn't reject the whole idea of a stackless interpreter
on the basis of that implementation, though. I suspect the
author used a special object because it got the job done with
minimal changes to the existing code. There are other ways to
signal to the interpreter what is to be done next.

> (another reason is that it can't be
> done in JPython so it can't be made a part of the language standard)

Has supporting JPython become an official guiding principle
behind mainstream Python development? That may turn out to be
somewhat restrictive in the long term.

Greg

Jeremy Hylton

unread,
Oct 15, 1999, 3:00:00 AM10/15/99
to Greg Ewing
>>>>> "GE" == Greg Ewing <greg....@compaq.com> writes:

GE> I suspect that, if you introduce read-only lexical scoping,
GE> you're just going to substitute the stream of "Why is there no
GE> #@%$#$% lexical scoping in Python" complaints with "Why the
GE> #@%$#$% can't I assign to a non-local variable in Python"...

I agree whole-heartedly with you here!

Jeremy

William Tanksley

unread,
Oct 17, 1999, 3:00:00 AM10/17/99
to
On Sun, 17 Oct 1999 22:47:49 +1000, skaller wrote:

>lookup fails in Viper, lookup continues for a method in the type object
>of the object. For the standard Python types, including integers,
>the type object I'm using is a class. So you can write:

> (1).abs()

This is totally orthogonal to your post, but I'm just curious: I like how
Dylan allows one to write the first argument of a function either as a
distinguished or ordinary paramter -- that is, "abs(1)" is the same as
"(1).abs()".

Can we do the same with Viper?

If so, can we also write function definitions with the same functionality,
as:

def self.__add__(addend)...

Of course, I'm about to ask for multiple dispatch, but since everyone who
even knows what multiple dispatch is already knew I was going to ask for
it... So I won't bother asking.

(Or did Guido use his time machine to clip my request out of this post?
If so, help me foil this conspiracy by igniting the debate about multiple
dispatch!)

my-old-server-is-<fnord>down-ly y'rs,
--
-William "Billy" Tanksley

Michael Hudson

unread,
Oct 18, 1999, 3:00:00 AM10/18/99
to
On Mon, 18 Oct 1999, Jeremy Hylton wrote:

> >>>>> "MH" == Michael Hudson <mw...@cam.ac.uk> writes:
>
> MH> PS: Given the bizarre reluctance of my university to give me any
> MH> work, can I ask if the source to viperi (at least) will be
> MH> perusable anytime soon?
>
> Suggestion for an interesting project if you don't have anything else
> to do: A Python compiler written in Python. For starters, it could
> just generate Python bytecode. But it would seem much easier to do
> analysis of Python code if we didn't have to muck with any C code to
> do it.

Uhh.. two problems.

1) I'm assuming I will have something to do, pretty soon.
2) I really don't get on with parsing & high level semantic analysis. I've
never poured much effort into it, but I certainly don't have any natural
flair for it.

Something I had been meaning to play with was extend the Python core to
incorporate a register machine as well as the stack machine we have at the
moment. This also strays far from my experience, but sounds more appealing
to me.

I was thinking of working from the existing bytecode and recompiling it to
my new vm. Not sure if this is wise.

But your idea is definitely a nice one. I think I'm more suited to
backends, me.

speculative-ly y'rs Michael


Neel Krishnaswami

unread,
Oct 19, 1999, 3:00:00 AM10/19/99
to
Michael Hudson <mw...@cam.ac.uk> wrote:
>On Mon, 18 Oct 1999, Jeremy Hylton wrote:
>>
>> Suggestion for an interesting project if you don't have anything else
>> to do: A Python compiler written in Python. For starters, it could
>> just generate Python bytecode. But it would seem much easier to do
>> analysis of Python code if we didn't have to muck with any C code to
>> do it.
>
>2) I really don't get on with parsing & high level semantic
>analysis. I've never poured much effort into it, but I certainly
>don't have any natural flair for it. [...] But your idea is

>definitely a nice one. I think I'm more suited to backends, me.

If you don't feel like parsing (and who does, really?), you can
use the parser module to generate ASTs to munch on.


Neel

Aahz Maruch

unread,
Oct 19, 1999, 3:00:00 AM10/19/99
to
In article <380CCF9A...@maxtal.com.au>,
skaller <ska...@maxtal.com.au> wrote:
>
> So the answer is: probably, I will release it for
>free evaluation (but not commercial use). But I'm not sure
>how to best approach this problem. Any ideas?

Talk to Sam Rushing, http://www.nightmare.com/

Jeremy Hylton

unread,
Oct 19, 1999, 3:00:00 AM10/19/99
to skaller
>>>>> "MS" == skaller <ska...@maxtal.com.au> writes:

MS> Michael Hudson wrote:
>> skaller <ska...@maxtal.com.au> writes:

>> Hold it there a second; you are proposing that knowing that x has
>> a certain type in the expression
>>
>> x = x + "hello"
>>
>> has an impact on inference?

MS> Yes. Rather, the type of x is _deduced_ from the fact that
MS> operator + must be string concatenation in this context, since
MS> the second argument is a string, and thus x must be a string.

I assume you're handling some of the special cases, and I'd be
interested to hear how.

It is easy enough to write a class that will do something when added
to a string. Arbitrary example:

class Foo:
def __init__(self):
self.strings = []
def __add__(self, other):
new = Foo()
new.strings = self.strings[:]
new.strings.append(other)
return new

x = Foo()
x = x + "hello"

I assume you can do some analysis to discover that x *might* be a
class that implements __add__. How specific is your analysis? If you
see a class like Foo, are all bets off about the meaning of x +
"hello"?

The second variation is a library that accepts some object as an
argument and adds "hello" to it. Do you do whole-program analysis to
determine whether any client of the library might pass something other
than a string? What are the implications of that for writing a
generic library?

Jeremy

skaller

unread,
Oct 20, 1999, 3:00:00 AM10/20/99
to
William Tanksley wrote:

> This is totally orthogonal to your post, but I'm just curious: I like how
> Dylan allows one to write the first argument of a function either as a
> distinguished or ordinary paramter -- that is, "abs(1)" is the same as
> "(1).abs()".
>
> Can we do the same with Viper?

No. I was going to implement it, but when I considered it,
I realised it wouldn't work well in Python. The problem is
several fold: in the first place, there's nothing magical
about function calls in python:

x.a (args)

is really:

(x.a) (args)

that is, an attribute lookup _followed_ by a function call.
So we would need to say that if

x.a

fails, try looking for 'a'. Worse, consider:

x.a = 1

Woops! Will this really assign to a local or module
variable, and not an attribute of the object? [No]

Anyhow, it started to look seriously inconsistent in Python.

> Of course, I'm about to ask for multiple dispatch, but since everyone who
> even knows what multiple dispatch is already knew I was going to ask for
> it... So I won't bother asking.

What do you want it to do? Any idea how it should work in Python?

skaller

unread,
Oct 20, 1999, 3:00:00 AM10/20/99
to
Michael Hudson wrote:
>
> skaller <ska...@maxtal.com.au> writes:

> Hold it there a second; you are proposing that knowing that x has a
> certain type in the expression
>
> x = x + "hello"
>
> has an impact on inference?

Yes. Rather, the type of x is _deduced_ from the fact that
operator + must be string concatenation in this context, since the


second
argument is a string, and thus x must be a string.

>That's a BIG sea change from CPython. Haven't you ever done this:
>
> option = raw_input("enter a parameter> ")
> try:
> option = int(option)
> except:
> print "get a Clue"
>
> OK, contrived example.

This is a good example! Viper will handle it!
It will know that 'option' is _either_ a string or an int.
[It _has_ to]

In other words, the type inference system WILL
work with ordinary Python, allowing a variable to
take values of multiple types. There will even be a special
handling for the most often used case:

try: option = some_function(args)
except: option = None

that is, 'option is either a string or None'.

> But the same name can refer to variables of
> different types at different times, so I don't think you can use that
> for inference.

Sure you can. However, the inference algorithm
is not the same as for a statically typed language.

> PS: Given the bizarre reluctance of my university to give me any work,
> can I ask if the source to viperi (at least) will be perusable anytime
> soon?

Don't know. Due to the bizarre reluctance of _anyone_ to give
me any work, I need to make some money somehow. I would like to do this
serving the computing community. I want Viper to be
free software (eventually), but I have to eat.

So the answer is: probably, I will release it for
free evaluation (but not commercial use). But I'm not sure
how to best approach this problem. Any ideas?

--

skaller

unread,
Oct 21, 1999, 3:00:00 AM10/21/99
to jer...@cnri.reston.va.us
Jeremy Hylton wrote:
>
> >>>>> "MS" == skaller <ska...@maxtal.com.au> writes:
>
> MS> Michael Hudson wrote:
> >> skaller <ska...@maxtal.com.au> writes:
>
> >> Hold it there a second; you are proposing that knowing that x has
> >> a certain type in the expression
> >>
> >> x = x + "hello"
> >>
> >> has an impact on inference?
>
> MS> Yes. Rather, the type of x is _deduced_ from the fact that
> MS> operator + must be string concatenation in this context, since
> MS> the second argument is a string, and thus x must be a string.
>
> I assume you're handling some of the special cases, and I'd be
> interested to hear how.

Please note: the inference system is not written yet.
I have done some tests to see if it can work (my results agree with
Jim Hunguins: yes, it can work).

The method is simple enough: each function (such as + )
has a set of possible signatures, such as:

int + int -> int
float + float -> float
string + string -> string

Now, when you see a +, there is a constraint on the arguments.
If one of the arguments, for example, is a string, then
given the three signatures above, the other has to be a string
too, and, the result is also a string.

Now, the list above is not complete for Python. For all
core language types, there is a fixed list of all the
functions and all the signatures: these are for operators
plus builtin functions.

In addition, user defined classes can do interesting things.
So we could allow, in most cases, the possibility that
the type is 'class instance'. We could go further and say
_which_ classes, by adding to these lists for each
class defining, say '__add__' or '__radd__'.

However, the really big speed improvements from
the type inference will come with core language
operations, where the overhead of lookup and
function call and allocations can be avoided
by using say, machine registers to hold
an integer.

This is already the case in some parts of Viper.
For example, Viper executes the loop:

for i in xrange(limit):

MUCH faster than Python. The time for 100000
iterations on my 166Mhz Pentium I machine
is ZERO second. Python takes 3 seconds.
That is because this case is hard coded in the interpreter
as a native for loop, which is compiled to machine
fast machine code.

> It is easy enough to write a class that will do something when added
> to a string. Arbitrary example:
>
> class Foo:
> def __init__(self):
> self.strings = []
> def __add__(self, other):
> new = Foo()
> new.strings = self.strings[:]
> new.strings.append(other)
> return new
>
> x = Foo()

> x = x + "hello"
>

> I assume you can do some analysis to discover that x *might* be a
> class that implements __add__. How specific is your analysis?

It is vague: I haven't written the code yet.

>If you
> see a class like Foo, are all bets off about the meaning of x +
> "hello"?

No. There are some rules for inference.
For example, consider:

if something: x = 1
else: x= "string"

Here, Viper should be able to tell that x is either an integer or
a string (and nothing else).

What Viper will do is glean all the available 'static' information.
What it will not do, is be able to do dynamic flow analysis.
{except in special idiomatic cases like 'if 1: .. '}

However, there is a trick. Viper compiles code by FIRST running
the interpreter Viperi to load the modules: in this stage,
stuff is imported. After this stage is finished,
THERE IS NO MODULE LEVEL CODE LEFT. All the code is in functions,
and the dictionaries of the modules are built.
So cases like:

# module level code ..
try: import x
except: x = None

will work, because they're actually interpreted: this code
is not compiled.


> The second variation is a library that accepts some object as an
> argument and adds "hello" to it. Do you do whole-program analysis to
> determine whether any client of the library might pass something other
> than a string?

Yes.

> What are the implications of that for writing a
> generic library?

None. Viperc does not build modules or packages,
it builds whole executable programs. So, a particular
module or function may be used differently in different
programs. [At least, this is the initial goal]

The reason for this choice, despite the appeal
of compiling modules, packages, or libraries, is simply
that experience of others indicates that the existing
python interpreter is so fast, compiling code which
is equivalent on a per module/per function kind of basis
is not really worth the effort.

The only way to gain any leverage is to
analyse a whole program, in which case many
possible uses of, say, a function like:

def f (x,y): return x + y

are eliminated because they're not in fact used
in a particular program.

William Tanksley

unread,
Oct 23, 1999, 3:00:00 AM10/23/99
to
On Wed, 20 Oct 1999 05:55:44 +1000, skaller wrote:
>William Tanksley wrote:
>> This is totally orthogonal to your post, but I'm just curious: I like how
>> Dylan allows one to write the first argument of a function either as a
>> distinguished or ordinary paramter -- that is, "abs(1)" is the same as
>> "(1).abs()".

>> Can we do the same with Viper?

> No. I was going to implement it, but when I considered it,
>I realised it wouldn't work well in Python. The problem is
>several fold: in the first place, there's nothing magical
>about function calls in python:

> x.a (args)

>is really:
> (x.a) (args)
>that is, an attribute lookup _followed_ by a function call.

Yes, I see. This does make this syntax effectively impossible. Okay,
just as well.

>So we would need to say that if
> x.a
>fails, try looking for 'a'. Worse, consider:

Actually, you'd always define x.a when you defined a globally. Not that
hard, just a huge waste of space and time :).

>Anyhow, it started to look seriously inconsistent in Python.

You know, I'd always thought of that as a problem with Python (although it
makes certain things simpler), but I just recently found the XML-RPC
client written in Python. Because Python is not supposed to have
knowledge of objects' internals, the XML-RPC calls are direct -- you just
write "xml_object.method(...)" rather than having to quote the names of
the remote methods, as you have to do with every other XML-RPC API.

Cool.

>> Of course, I'm about to ask for multiple dispatch, but since everyone who
>> even knows what multiple dispatch is already knew I was going to ask for
>> it... So I won't bother asking.

> What do you want it to do? Any idea how it should work in Python?

Multiple dispatch. Hmm...

Okay, the type of OO you're familiar with is also known as single dispatch
-- you take a message, a bunch of parameters, and a special parameter
(called 'this' or 'self'), and you look up the meaning of the message in
self's method table.

With multiple dispatch, OTOH, you take a message and a bunch of
parameters, and you look up the meaning of the message in the message's
parameter table.

You can think of it as being like a combination of overloading and
templates, but resolvable at runtime. Thus, it's MUCH more powerful
without being more risky -- in fact, it's less risky than most
implementations of overloading (C++'s especially).

>John Skaller, mailto:ska...@maxtal.com.au

--
-William "Billy" Tanksley

Gordon McMillan

unread,
Oct 24, 1999, 3:00:00 AM10/24/99
to William Tanksley, pytho...@python.org
[William Tanksley]

> >> Of course, I'm about to ask for multiple dispatch, but since
> >> everyone who even knows what multiple dispatch is already knew
> >> I was going to ask for it... So I won't bother asking.

[John Skaller]

> > What do you want it to do? Any idea how it should work in
> >Python?
>
> Multiple dispatch. Hmm...
>
> Okay, the type of OO you're familiar with is also known as single
> dispatch -- you take a message, a bunch of parameters, and a
> special parameter (called 'this' or 'self'), and you look up the
> meaning of the message in self's method table.
>
> With multiple dispatch, OTOH, you take a message and a bunch of
> parameters, and you look up the meaning of the message in the
> message's parameter table.

It's called "multiple dispatch" because, I think, you're
supposed to dispatch on both message and self/this.

In C++ or Java, this shows up as trying to resolve both an
override and an overload at the same time. That is:

<base_class1_ptr>->method(<base_class2_ptr>)

which you want handled by the appropriate

DerivedClass1::method(DerivedClass2)

In C++ / Java, this means bouncing the message back and
forth -

DerivedClass1::method(BaseClass2 *thing)
{
thing->inversemethod(this);
}

DerivedClass2::inversemethod(DerivedClass1 *otherthing)
{
//now we have it resolved
}

Unfortunately, this means creating scores of virtual methods
with no purpose except allowing the compiler to find the trail.

But overriding is no problem in Python - the compiler never
gets in the way. And overloading is impossible, so that's no
problem either <wink>. If you want to avoid the if/elif
statement, you could write BaseClass1's method as

apply(dispatchdict[thing.__class__], ....)

and have methodTakingLarch,
methodTakingSmallFuzzyThing, ...

Or even

mthd = getattr(self, "methodTaking" +
thing.__class__.__name__)

The real point being that before you ask for dual dispatch,
you'd better ask for overloading first...

- Gordon

William Tanksley

unread,
Oct 24, 1999, 3:00:00 AM10/24/99
to
On Sun, 24 Oct 1999 08:07:20 -0400, Gordon McMillan wrote:
>[William Tanksley]

>> >> Of course, I'm about to ask for multiple dispatch, but since
>> >> everyone who even knows what multiple dispatch is already knew
>> >> I was going to ask for it... So I won't bother asking.

>[John Skaller]
>> > What do you want it to do? Any idea how it should work in
>> >Python?

>> Multiple dispatch. Hmm...

>> Okay, the type of OO you're familiar with is also known as single
>> dispatch -- you take a message, a bunch of parameters, and a
>> special parameter (called 'this' or 'self'), and you look up the
>> meaning of the message in self's method table.

>> With multiple dispatch, OTOH, you take a message and a bunch of
>> parameters, and you look up the meaning of the message in the
>> message's parameter table.

>It's called "multiple dispatch" because, I think, you're
>supposed to dispatch on both message and self/this.

Dear me, no. I'm sorry I was unclear -- it's multiple dispatch because
you dispatch on every single parameter to the call, not just a special one
called 'this'.

>In C++ or Java, this shows up as trying to resolve both an
>override and an overload at the same time. That is:

Nope -- although like using non-virtual functions in C++ to handle OO,
it's close so long as you don't ever inherit.

>Unfortunately, this means creating scores of virtual methods
>with no purpose except allowing the compiler to find the trail.

No, it's just a different way of storing functions -- instead of storing
messages together with methods inside an object, you store the messages
globally and only the methods locally.

Of course, this is very different from how Python works now, so I actually
think it's not that good of an idea :-). Python can be a good language
without having to use every good idea other language have -- that's what
Dylan is for.

>The real point being that before you ask for dual dispatch,
>you'd better ask for overloading first...

Never! Overloading is a certain way to break multiple dispatch. MD can
handle everything overloading does, but it can also do it at runtime, the
way Python prefers to work.

Now, we WOULD need typed method declarations before we could use multiple
dispatch.

My judgement: not worth bothering. It's really nice, but if I need it I
can always use CLOS.

>- Gordon

--
-William "Billy" Tanksley

skaller

unread,
Oct 26, 1999, 3:00:00 AM10/26/99
to William Tanksley
William Tanksley wrote:

> Multiple dispatch. Hmm...
>
> Okay, the type of OO you're familiar with is also known as single dispatch
> -- you take a message, a bunch of parameters, and a special parameter
> (called 'this' or 'self'), and you look up the meaning of the message in
> self's method table.
>
> With multiple dispatch, OTOH, you take a message and a bunch of
> parameters, and you look up the meaning of the message in the message's
> parameter table.

I see. I had never thought about it this way, despite
reading some theory based on this idea, you have put this
very simply. Thanks!

Hmm. This isn't multiple dispatch, really,
but really something even more powerful: the dispatcher
for a message can do _anything_ to decide which
function to call, and how to convert the arguments.
That is, it would be possible to specialised
the dispatcher for each message kind, depending
on what makes sense for that kind of message.

I'll have to think about this, and maybe
go back and re-read the OO theory articles I read
based on this idea.

skaller

unread,
Oct 26, 1999, 3:00:00 AM10/26/99
to gm...@hypernet.com
Gordon McMillan wrote:

> In C++ or Java, this shows up as trying to resolve both an
> override and an overload at the same time. That is:
>
> <base_class1_ptr>->method(<base_class2_ptr>)
>
> which you want handled by the appropriate
>
> DerivedClass1::method(DerivedClass2)
>
> In C++ / Java, this means bouncing the message back and
> forth -

> Unfortunately, this means creating scores of virtual methods
> with no purpose except allowing the compiler to find the trail.

No: 'unfortunately', this technique does not
work except in a few special cases. It is utterly
impossible to implement general binary operators using
this technique, for example, and they're pretty much
the simplest case.

The 'reason' it doesn't work is that the
'scores of virtual methods' are unbounded, which
violates encapsulation. Thus, the technique
does not, in fact, work at all.

It is used in cases where inheritance
is abused because the language does not support
the coproduct (sum/variant/discriminated
union) construction, which is actually desired:
here the number of 'cases' is known and finite,
but the use of subtyping is utterly wrong for
this construction.

> But overriding is no problem in Python - the compiler never
> gets in the way. And overloading is impossible, so that's no
> problem either <wink>.

Yes: Python isn't statically typed, which makes
many things work very smoothly, albiet with more risk
than statically typed languages.

> The real point being that before you ask for dual dispatch,
> you'd better ask for overloading first...

I see.

skaller

unread,
Oct 26, 1999, 3:00:00 AM10/26/99
to William Tanksley
William Tanksley wrote:

> Now, we WOULD need typed method declarations before we could use multiple
> dispatch.
>
> My judgement: not worth bothering. It's really nice, but if I need it I
> can always use CLOS.

Hmm. I'm not so sure. Viper uses 'multiple dispatch' in the
sense of doing case analysis on argument types. For example,
to execute python's

a + b

it checks the types of a and b, may coerce the arguments,
and then calls an appropriate function.

Now, the cases I handle are fixed (int, string, sequence ..),
but it would be interesting to ask: if I add a new type
to the language, how can I incorporate it into the
dispatcher?

Python allows you to do that in only a fixed way,
by defining an __add__ (and/or __raddd__) method
for a new class. This is basically wrong, because
there is no way you can handle adding a new type A
and a new type B together, except by 'mutating'
one or both of the __add__ method 'after the fact'.

It would be more useful to add a handler for
each combination to a table for '+', that could
include A + B (after both A and B are defined,
and a way of adding then is written).

This is also more convenient for a type inference
engine, which likes tables of possible argument types
for functions like '+'.

There is one more advantage (and it is a killer):
it is a serious problem, adding new operators to python,
because the 'vtable' in the CAPI has to be extended for
each such operator.

But using the 'multiple dispatch' approach, or, rather,
the 'per message' approach, the problem goes away:
each operator just has an extensible table of
cases which can be extended as required.

Louis Madon

unread,
Oct 26, 1999, 3:00:00 AM10/26/99
to
skaller wrote:
>
> William Tanksley wrote:
> > With multiple dispatch, OTOH, you take a message and a bunch of
> > parameters, and you look up the meaning of the message in the message's
> > parameter table.
>
> I see. I had never thought about it this way, despite
> reading some theory based on this idea, you have put this
> very simply. Thanks!
>
> Hmm. This isn't multiple dispatch, really,
> but really something even more powerful: the dispatcher
> for a message can do _anything_ to decide which
> function to call, and how to convert the arguments.
> That is, it would be possible to specialised
> the dispatcher for each message kind, depending
> on what makes sense for that kind of message.
>
> I'll have to think about this, and maybe
> go back and re-read the OO theory articles I read
> based on this idea.

Well, what your describing sounds alot like predicate dispatch. You may
want to take a look at the article, "Predicate Dispatching: A Unified
Theory of Dispatch" located at
http://www.cs.washington.edu/research/projects/cecil/www/Papers/papers.html

Louis.

Greg Ewing

unread,
Oct 27, 1999, 3:00:00 AM10/27/99
to
Multiple dispatch is an attractive idea, but it makes me uneasy.
One reason is that it's not clear what the rules should be.

Suppose AA is a subclass of A, and BB is a subclass of B,
and there are methods defined for the combinations
foo(A, B), foo(AA, B) and foo(A, BB).

Now, if you make a call with the combination foo(AA, BB),
which method should get called?

Arbitrary rules can of course be invented, but there is
no single choice that is obviously "right" the way there
is with single dispatch.

Multiple inheritance has a similar problem, which is why
I'm not all that keen on it either, unless used in a very
restricted way.

If you have multiple dispatch *and* multiple inheritance,
things get just too hairy to contemplate. I tried to devise
a set of rules for this case once, but my brain got dangerously
close to exploding and I had to give up...

Greg

Neel Krishnaswami

unread,
Oct 27, 1999, 3:00:00 AM10/27/99
to
Greg Ewing <greg....@compaq.com> wrote:
>Multiple dispatch is an attractive idea, but it makes me uneasy.
>One reason is that it's not clear what the rules should be.
>
>Suppose AA is a subclass of A, and BB is a subclass of B,
>and there are methods defined for the combinations
>foo(A, B), foo(AA, B) and foo(A, BB).
>
>Now, if you make a call with the combination foo(AA, BB),
>which method should get called?

None of them; an "ambiguous method" exception should be raised. (Or if
you are in a statically-typed language ambiguous method dispatches can
be a compile-time error.) Seriously, an ambiguous method dispatch is a
sign of a problematic design.

The corresponding code in a singly-dispatching language is likely to
be a manual implementation of multiple dispatch using type tests on
the non-receiver arguments. IMO this is an accident waiting to happen,
since the method chosen can depend on the details of the order of the
type tests.

>If you have multiple dispatch *and* multiple inheritance,
>things get just too hairy to contemplate. I tried to devise
>a set of rules for this case once, but my brain got dangerously
>close to exploding and I had to give up...

I have done some programming in Dylan (which has MD and MI), and I
have never run into the problems you describe. I suppose that in
poorly-designed code it's possible, but the corresponding problems of
proliferating typecases and code duplication in singly-dispatching and
single-inheritance languages are sufficiently worse that I prefer MD
and MI.

Mileage varies, I guess.


Neel

Tim Peters

unread,
Oct 27, 1999, 3:00:00 AM10/27/99
to pytho...@python.org
[Greg Ewing]

> Multiple dispatch is an attractive idea, but it makes me uneasy.
> One reason is that it's not clear what the rules should be.
>
> Suppose AA is a subclass of A, and BB is a subclass of B,
> and there are methods defined for the combinations
> foo(A, B), foo(AA, B) and foo(A, BB).
>
> Now, if you make a call with the combination foo(AA, BB),
> which method should get called?
>
> Arbitrary rules can of course be invented, but there is
> no single choice that is obviously "right" the way there
> is with single dispatch.

The first language I saw that took multimethods seriously was Cecil. As is
often the case for pioneers, they answered the question in *the* obviously
right way, then waited a decade for everyone else to realize that subsequent
"improvements" weren't <wink>: in Cecil, it's an error to call foo(AA, BB),
precisely because it's hopelessly ambiguous.

In the absence of static typing, this makes for a curious new class of
error: adding a new multimethod can cause previously-working code to blow
up at runtime, if the new MM introduces new ambiguities (e.g., if only
foo(A,B) and foo(AA, B) were defined, foo(AA, BB) was called and resolved to
the latter, then you add a new foo(A, BB)). You really, really, really want
to get told about that at compile-time.

There's much interesting reading (including rigorous definition of Cecil's
dispatching scheme) at:

http://www.cs.washington.edu/research/projects/cecil/www/Papers/papers.html

your-brain-won't-explode-but-your-compiler-might<wink>-ly y'rs - tim

Tim Peters

unread,
Oct 27, 1999, 3:00:00 AM10/27/99
to pytho...@python.org
[Tim]

> The first language I saw that took multimethods seriously was Cecil
> ...

[John Skaller]
> The main thing about Cecil, though, is that
>
> a) it is prototype based
> b) it throws out encapsulation

They would not agree with #b. This is argued at length in the referenced
papers.

part-of-taking-mm-seriously-is-giving-up-file-based-source-code-ly y'rs -
tim

Louis Madon

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to
Greg Ewing wrote: > Multiple dispatch is an attractive idea, but it makes me uneasy. > One reason is that it's not clear what the rules should be. > Suppose AA is a subclass of A, and BB is a subclass of B, > and there are methods defined for the combinations > foo(A, B), foo(AA, B) and foo(A, BB). > Now, if you make a call with the combination foo(AA, BB), > which method should get called? Neither, you should get a "message ambiguous" error unless (as you say below) some arbitrary rule is used to disambiguate. > Arbitrary rules can of course be invented, but there is > no single choice that is obviously "right" the way there > is with single dispatch. Yes, but why is having a "single choice" so important? There is an analogy here with static vs. dynamic typing. In a statically typed language there is always a well defined (ie. single choice) type for each variable so you know you'll never get a type error at runtime. Not so with dynamic typing, nonetheless many people prefer that for numerous reasons (eg. greater flexibility), and argue that type errors can be dealt with during testing. Similarly, multiple dispatch gives you a more expressive object system - thus permitting simpler, more reusable class models in many cases - so dealing with the occasional ambiguous message error during testing seems a small price to pay. > Multiple inheritance has a similar problem, which is why > I'm not all that keen on it either, unless used in a very > restricted way. But if the domain you need to model isn't naturally hierarchical you'll end up with redundancy in your class model making the projects' evolution and maintenance more tedious. No MI is Ok if the project is relatively small, but as you scale things up the redundancy problem just keeps getting worse. [Admittedly, MI is not implemented to well in some languages, eg, c++, but thats no reason to throw the baby out with the bath water]. > If you have multiple dispatch *and* multiple inheritance, > things get just too hairy to contemplate. I tried to devise > a set of rules for this case once, but my brain got dangerously > close to exploding and I had to give up... Can you elaborate? I mean about MD + MI (Its ok, I'll keep a safe distance from you so feel free to self destruct while considering this :-) Louis

skaller

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to Tim Peters
Tim Peters wrote:

> The first language I saw that took multimethods seriously was Cecil. As is
> often the case for pioneers, they answered the question in *the* obviously
> right way, then waited a decade for everyone else to realize that subsequent
> "improvements" weren't <wink>

The main thing about Cecil, though, is that

a) it is prototype based
b) it throws out encapsulation

It is not clear to me that (a) is required for multi-methods,
but (b) definitely is. _proper_ multimethods cannot
coexist with class based object orientation.

Since python doesn't provide encapsulation, (it is object
based, not object oriented) MM might work,
and if it could be done, it would determine that
(a) wasn't required.

Louis Madon

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to
Greg Ewing wrote:
> I'm a bit bothered by the idea that *adding* a new
> method could cause previously valid calls to become
> invalid. If I have only foo(A, B) and foo(AA, B)
> defined, then calling foo(AA, BB) is okay. But if
> I add a foo(A, BB), then all my existing foo(AA, BB)
> calls, anywhere in the program, suddenly become ambiguous.

This scenario is telling you something about your architecture: perhaps
you shouldn't be calling foo(AA, BB) or perhaps you should do some
refactoring of the class model or perhaps you need to add a specific
foo(AA, BB) method. IMHO, this process helps to produce a better
architecture.

>
> I worry that it will be difficult to reason about the
> behaviour of a program with MD

Without MD, programmers will invent adhoc workarounds to simulate it
(eg. explicit type tests or adding extra levels of indirection like in
the visitor pattern). Now lets say you've got to work on a 50kloc
program that was originally written by someone else, do you really think
that having such adhoc mechanisms sprinkled throughout the code makes
reasoning *easier*? I think the opposite is true; use of well defined
abstractions leads to easier to understand systems.

> because the information
> needed to determine which method a given call will
> invoke is spread out over many places, with no clear
> way of finding all of them. If I'm looking for the
> implementation of foo(A, B), will it be in the module
> defining A, or B? Or somewhere else entirely?
> What about foo(A, B, C, D, E)? It seems that I have
> a lot more possible places to look when I'm trying
> to find a piece of code.

I see the issue of finding where the various bits of code are as
separate to the notion of "reasoning about behaviour". A decent code
browser should be able to automate this process for you. In Harlequin
Dylan for example, the IDE lets you navigate through your code in a
variety of ways including looking up related methods.

> I was thinking about it in the context of an interactive
> fiction authoring language. MD seems like an attractive idea
> there, because often a verb needs to be specialised in ways
> that depend equally much on all its arguments.

...

> Eventually I decided that what I really wanted was a set
> of pattern-matching clauses, like in functional languages.
> But for that to work it has to be clear what order the
> matches are tried in, which means that all the possible
> branches of an action really have to be listed together.
> That makes it difficult to have libraries of code which
> can be extended by the programmer.

Well your reasoning may well be sound here; MD is more expressive than
SD but that doesn't make it a solution for everything. That's why I
find predicate dispatch so interesting, it generalises concepts like MD,
pattern matching, unification and others into a single elegant theory.
Unfortunately, AFAIK, there is no language that implements it.

Louis.

Martijn Faassen

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to
skaller <ska...@maxtal.com.au> wrote:
[snip]

> Since python doesn't provide encapsulation, (it is object
> based, not object oriented)

Not to start a flamewar, but I haven't seen this definition of 'object based'
before. I thought object based languages had classes with methods, but no
inheritance.

If encapsulation is the key for Real Object Orientedness (tm), then Smalltalk,
one of the prototypical object oriented languages, wouldn't be object oriented
either. Seems a bit odd.

Regards,

Martijn
--
History of the 20th Century: WW1, WW2, WW3?
No, WWW -- Could we be going in the right direction?

Fredrik Lundh

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to Python Mailing List
skaller <ska...@maxtal.com.au> wrote:
> Since python doesn't provide encapsulation, (it is object
> based, not object oriented)

what's the color of the sky on your planet?

</F>


Martin von Loewis

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to
Greg Ewing <greg....@compaq.com> writes:

> > Neither, you should get a "message ambiguous" error
>

> That's a reasonable answer. Although you still need some
> set of rules for defining when a call is ambiguous or
> not. I think I can sort of see what rule you're using
> here, but it's not easy to state concisely.

You can phrase the requirement two different ways: Given a the
half-ordered set of signatures (sig a is 'more derived' than sig b if
all arguments are 'more derived'), there is a sufficient static
requirement:

The set of signatures must form a lattice.

(i.e. even though it is only a half order, for each pair (a,b) of
signatures that don't compare, there must be a third signature c,
with c 'more derived' than a and c 'more derived' than b).

Alternatively, you could phrase it as a dynamic condition (which would
be more Python-like): Given an invocation, there is a set of candidate
signatures (so that the actual argument types are 'more derived' than
the paramter types of each candidate). The invocation is ambiguous if
this set of candidates is not a lattice.

Hope this helps,
Martin

Amit Patel

unread,
Oct 28, 1999, 3:00:00 AM10/28/99
to
Louis Madon <mad...@bigfoot.com> wrote:
| Greg Ewing wrote:
| > I'm a bit bothered by the idea that *adding* a new
| > method could cause previously valid calls to become
| > invalid. If I have only foo(A, B) and foo(AA, B)
| > defined, then calling foo(AA, BB) is okay. But if
| > I add a foo(A, BB), then all my existing foo(AA, BB)
| > calls, anywhere in the program, suddenly become ambiguous.
|
| This scenario is telling you something about your architecture: perhaps
| you shouldn't be calling foo(AA, BB) or perhaps you should do some
| refactoring of the class model or perhaps you need to add a specific
| foo(AA, BB) method. IMHO, this process helps to produce a better
| architecture.

I don't know I'm calling foo(AA, BB). All I know is that I have an "A
like" object and a "B like" object (Python examples: file-like object
and sequence). I then call foo on these two arguments, and the system
complains.

Therefore it can't be my fault when the foo(AA, BB) call fails.

My caller (1) doesn't know I'm calling foo (after all, why should he
know the implementation details of my class?), and (2) only gave me
one of the two arguments, and therefore is unaware of the combination
that is about to take place.

Therefore it can't be my caller's fault when foo(AA, BB) fails.

When defining foo(AA, B), the author of AA may not know about foo(A,
BB), so it can't be AA's fault if the call fails.

When defining foo(A, BB), the