Death to tuples!

Antoon Pardon apardon at forel.vub.ac.be
Fri Dec 2 14:05:43 CET 2005


On 2005-12-02, Bengt Richter <bokr at oz.net> wrote:
> On 1 Dec 2005 09:24:30 GMT, Antoon Pardon <apardon at forel.vub.ac.be> wrote:
>
>>On 2005-11-30, Duncan Booth <duncan.booth at invalid.invalid> wrote:
>>> Antoon Pardon wrote:
>>>
>>>>> The left one is equivalent to:
>>>>>
>>>>> __anon = []
>>>>> def Foo(l):
>>>>>    ...
>>>>>
>>>>> Foo(__anon)
>>>>> Foo(__anon)
>>>> 
>>>> So, why shouldn't: 
>>>> 
>>>>    res = []
>>>>    for i in range(10):
>>>>       res.append(i*i)
>>>> 
>>>> be equivallent to:
>>>> 
>>>>   __anon = list()
>>>>   ...
>>>> 
>>>>    res = __anon
>>>>    for i in range(10):
>>>>       res.append(i*i)
>>>
>>> Because the empty list expression '[]' is evaluated when the expression 
>>> containing it is executed.
>>
>>This doesn't follow. It is not because this is how it is now, that that
>>is the way it should be.
>>
>>I think one could argue that since '[]' is normally evaluated when
>>the expression containing it is excuted, it should also be executed
>>when a function is called, where '[]' is contained in the expression
>  ^^^^^^^^^^^^^^^^^^^^^^^[1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>determining the default value.
>  ^^^^^^^^^^^^^^^^^^^^^^^^^^^[2]
> Ok, but "[]" (without the quotes) is just one possible expression, so
> presumably you have to follow your rules for all default value expressions.
> Plain [] evaluates to a fresh new empty list whenever it is evaluated,

Yes, one of the questions I have is why python people whould consider
it a problem if it wasn't.

Personnaly I expect the following pieces of code

  a = <const expression>
  b = <same expression>

to be equivallent with

  a = <const expression>
  b = a

But that isn't the case when the const expression is a list.

A person looking at:

  a = [1 , 2]

sees something resembling

  a = (1 , 2)

Yet the two are treated very differently. As far as I understand the
first is translated into somekind of list((1,2)) statement while
the second is build at compile time and just bound.

This seems to go against the pythonic spirit of explicit is
better than implicit.

It also seems to go against the way default arguments are treated.

> but that's independent of scope. An expression in general may depend on
> names that have to be looked up, which requires not only a place to look
> for them, but also persistence of the name bindings. so def foo(arg=PI*func(x)): ...
> means that at call-time you would have to find 'PI', 'func', and 'x' somewhere.
> Where & how?
> 1) If they should be re-evaluated in the enclosing scope, as default arg expressions
> are now, you can just write foo(PI*func(x)) as your call.

I may be a bit pedantic. (Read that as I probably am)

But you can't necesarry write foo(PI*func(x)) as your call, because PI
and func maybe not within scope where the call is made.

> So you would be asking
> for foo() to be an abbreviation of that. Which would give you a fresh list if
> foo was defined def foo(arg=[]): ...

This was my first thought.

> Of course, if you wanted just the expression value as now at def time, you could write
> def foo(...):...; foo.__default0=PI*fun(x) and later call foo(foo.__default0), which is
> what foo() effectively does now.
>
> 2) Or did you want the def code to look up the bindings at def time and save them
> in, say, a tuple __deftup0=(PI, func, x) that captures the def-time bindings in the scope
> enclosing the def, so that when foo is called, it can do arg = _deftup0[0]*_deftup0[1](_deftup0[2])
> to initialize arg and maybe trigger some side effects at call time.

This is tricky, I think it would depend on how foo(arg=[]) would be
translated. 

   2a) _deftup0=([]), with a subsequent arg = _deftup0[0]
or 
   2b) _deftup0=(list, ()), with subsequently arg = _deftup0[0](_deftup0[1])


My feeling is that this proposal would create a lot of confusion.

Something like def f(arg = s) might give very different results
depending on s being a list or a tuple.

> 3) Or did you want to save the names themselves, __default0_names=('PI', 'func', 'x')
> and look them up at foo call time, which is tricky as things are now, but could be done?

No, this would make for some kind of dynamic scoping, I don't think it
would mingle with the static scoping python has now.


>>>>> The left has one list created outside the body of the function, the
>>>>> right one has two lists created outside the body of the function. Why
>>>>> on earth should these be the same?
>>>> 
>>>> Why on earth should it be the same list, when a function is called
>>>> and is provided with a list as a default argument?
> It's not "provided with a list" -- it's provided with a _reference_ to a list.
> You know this by now, I think. Do you want clone-object-on-new-reference semantics?
> A sort of indirect value semantics? If you do, and you think that ought to be
> default semantics, you don't want Python. OTOH, if you want a specific effect,
> why not look for a way to do it either within python, or as a graceful syntactic
> enhancement to python? E.g., def foo(arg{expr}):... could mean evaluate arg as you would like.
> Now the ball is in your court to define "as you would like" (exactly and precisely ;-)

I didn't start my question because I wanted something to change in
python. It was just something I wondered about. Now I wouldn't
mind python to be enhanced at this point, so should the python
people decide to work on this, I'll give you my proposal. Using your
syntax.

  def foo(arg{expr}):
     ...

should be translated something like:

  class _def: pass

  def foo(arg = _def):
    if arg is _def:
      arg = expr
    ...

I think this is equivallent with your first proposal and probably
not worth the trouble, since it is not that difficult to get
the behaviour one wants.

I think such a proposal would be most advantaged for the newbees
because the two possibilities for default values would make them
think about what the differences are between the two, so they
are less likely to be confused about the def f(l=[]) case.

>>> Because the empty list expression '[]' is evaluated when the 
>>> expression containing it is executed.
>>
>>Again you are just stating the specific choice python has made.
>>Not why they made this choice.
> Why are you interested in the answer to this question? ;-)

Because my impression is that a number of decisions were made
that are inconsistent with each other. I'm just trying to
understand how that came about.

> Do you want
> to write an accurate historical account, or are you expressing discomfort
> from having had to revise your mental model of other programming languages
> to fit Python? Or do you want to try to enhance Python in some way?

If there is discomfort, then that has more to do with having revised
my mental model to python in one aspect doesn't translate to
understanding other aspects of python enough.

>>I know what happens, I would like to know, why they made this choice.
>>One could argue that the expression for the default argument belongs
>>to the code for the function and thus should be executed at call time.
>>Not at definion time. Just as other expressions in the function are
>>not evaluated at definition time.
> Maybe it was just easier, and worked very well, and no one showed a need
> for doing it differently that couldn't easily be handled. If you want
> an expression evaluated at call time, why don't you write it at the top
> of the function body instead of lobbying for a change to the default arg
> semantics?

I'm not lobbying for a change. You are probably right that this is again
the "Practical beats purity" rule working again. But IMO the python
people are making use of that rule too much, making the total language
less pratical as a whole.

Purity is often practical, because it makes it easier to infer knowlegde
from things you already know. If you break the purity for the practical
you may make one specific aspect easier to understand, but make it
less practical to understand the language as a whole.

Personnally I'm someone for whom purity is practical in most cases.
If a language is pure/consistent it makes the langauge easier to
learn and understand, because your knowledge of one part of the
language will carry over to other parts.

Isn't is practical that strings tuples and list all treat '[]'
similarly for accessing an individual in the sequence. That means
I just have to learn what v[x] means for tuples and I know what
it means for lists, strings and a lot of other things.

Having a count method for lists but not for tuples breaks that
consistency and makes that I have to look it up for each sequence
whether or not it has that method. Not that practical IMO.

> [ ... ]
>
> or what are we pursuing?

What I'm pursuing I think is that people would think about what
impractical effects can arise when you drop purity for practicallity.

My impression is that when purity is balanced against practicallity
this balancing is only done on a local scale without considering
what practicallity is lost over the whole language by persuing
praticallity on a local aspect.

-- 
Antoon Pardon



More information about the Python-list mailing list