why cannot assign to function call

Mark Wooding mdw at distorted.org.uk
Sat Jan 10 10:46:35 EST 2009


[Another tome.  I hope this contains sufficient new material to continue
to be of interest to other readers.]

Steven D'Aprano <steve at REMOVE-THIS-cybersource.com.au> wrote:
> On Fri, 09 Jan 2009 20:23:11 +0000, Mark Wooding wrote:
>
> > No: not directly.  The Python program deals solely with references;
> > anything involving actual objects is mediated by the runtime.
>
> Your claim is ambiguous: when you say "the Python program", are you
> talking about the program I write using the Python language, or the
> Python VM, or something else? 

The former.

> If the first, then you are wrong: the Python program I write doesn't
> deal with references. It deals with objects.

Do you have evidence for this claim other than your vigorous assertion?
I have evidence: in particular, after the example

  l = [1, 2, 3]
  l[1] = l

the list l (abuse of terminology: I mean `the list referred to from the
location bound to l') has the structure I drew, but you snipped:

                 ,----------,
                 v          |
      +---+    +---+        |
   l: | *----> | *-----> 1  |
      +---+    +---+        |
               | *----------'
               +---+
               | *-----> 3
               +---+

Can you explain, without using the concept of references (or anything
else equivalent) why this happens, and why the result is not (for
example) [1, [1, 2, 3], 3]?

> This discussion flounders because we conflate multiple levels of
> explanation.

No.  I'm sticking to a single level of explanation.  It may appear to be
a lower level than the one you're trying to promote, and therefore
contains more detail and is more complicated, but it has the benefit
that it explains observed phenomena.

> People say "You do foo" when they mean "the Python VM does foo".

Since what the programmer does is type at a keyboard, the keystrokes
being interpreted eventually as editing commands in a text editor
(having been processed by various intermediate pieces of software
probably including keyboard drivers, and maybe window systems or
terminal line disciplines), anything else is an abuse of terminology;
but a useful one.

So, the programmer types, with the objective (presumably) of
constructing a text file containing a Python program which, when
executed by an appropriate Python implementation, will behave in a
satisfactory way.

The notion of `Python VM' is a good one (though potentially open to
confusion -- alas, `virtual machine' has multiple meanings too).  So: a
Python program can be interpreted as instructions to such a virtual
machine, to behave in particular ways.  Agreed?  The question is: how do
we best describe the behaviour of such a Python VM when executing a
Python program, given its text?

It's far too easy, as we've both shown, to slip into imprecise
terminology here.  When I write

> > You bind names to locations which store immediate representations.
> > Python IRs are (in the sense defined above) exclusively references.

I mean, of course, that a Python implementation behaves is if it binds
`names to locations which store immediate representations'.

> I most certainly don't bind names to locations. When I write x=23, I 
> don't know what the location of the object 23 is, so how could I bind 
> that location to the name?

Quite.  A lapse on my part, due to sloppy writing caused by a desire not
to make this article any longer than it already was.

> You are conflating what the Python VM does with what I do.

No...

> What *I* do is bind the object 23 to the name x. 

What you do is described above.  It doesn't involve any binding at all,
or objects.  Unless, that is, you are actually implementing Python
personally (i.e., not merely instructing a computer to do so, but
actually within your own mind); in which case I claim that you must do
so as I have described, or do so wrongly.

> I don't know the location of 23, I don't even know if 23 has a
> well-defined location

I warned that I was using certain terms in a technical sense, and
attempted to define them clearly at the top of my article.  The word
`location' was one such:

:   * A /location/[1] is an area of memory suitable for storing the
:     /immediate representation/ (which I shall abbreviate to /IR/) of a
:     value.  (A location may be capable of storing things other than
:     IRs, e.g., representations of unevaluated expressions in lazily
:     evaluated languages.  Locations may vary in size, e.g., in order
:     to be capable of storing different types of IRs.)

Whether 23 has a location is unimportant.  In the context of your
example `x = 23', what's important is that the name `x' is bound to a
location, and then an immediate representation of the value `23' is
stored in that location.

(This description holds for all languages I can think of, modulo details
of which occurrences of names are binding occurrences, and C++'s user-
controlled assignment and so on.)

> or if it is some sort of distributed virtual data structure. As a
> Python programmer, that's the level I see: names and objects.

Only if you want to be confused.  Without references one is confused by
circular data structures; without locations, one is confused by closures
and assignment.

There are /two/ layers of indirection between variable names and values
in Python.  One layer is the mapping between names and locations: the
usual term for this mapping is an `environment', and the modification of
this mapping is termed `binding'.  The other layer is the mapping from
locations to values; modification of this mapping is called
`assignment'.

As an example, I had this conversation with Python 3.0rc1.  (Python 2
can't illustrate the situation as clearly since it doesn't have
`nonlocal'.)

>>> ff = [lambda: x for x in [1, 2, 3]]
>>> ff[0]()
3
>>> ff[1]()
3

>>> def getset(x):
...   def get(): return x
...   def set(y): nonlocal x; x = y
...   return get, set
...
>>> g, s = getset(42)
>>> g()
42
>>> s(69)
>>> g()
69

>>> ff = [getset(x) for x in [1, 2, 3]]
>>> ff[0][0]()
1
>>> ff[1][0]()
2
>>> ff[1][1](5)
>>> ff[1][0]()
5
>>> ff[0][0]()
1
>>> ff[2][0]()
3

Again, your challenge is to explain these phenomena -- all of which, I
believe are required behaviour of Python 3 -- without talking about both
levels of indirection.

> At a lower level, what the Python VM does is store the name 'x' in a
> dictionary, bound to the object 23. No locations come into it, because
> at this level of explanation, dictionaries are an abstract mapping.

You've misunderstood my definition of `location'.  Here, the dictionary
entry for the key `x' would indeed be the location.

As a matter of implementation detail, a `dictionary', as understood by
Python users as a kind of Python value which implements an associative
array, is not actually used to deal with local variables at run-time.
Rather, each bound variable is given an index into a vector allocated at
function-entry time; variables which are bound in an outer function are
also given indices, and looked up in a different vector, allocated when
the closure was constructed.

> There's no requirement that the abstract dictionary structure works by
> storing addresses. 

I'd hoped I'd been very careful to avoid talking about addresses!
Indeed, I'd gone as far as saying that a location was an area of memory,
rather than the address of an area of memory, specifically to avoid that
trap.

Maybe it'll help at this point if I explain that all of the machinery I
described above, together with the definitions of pass-by-value and
pass-by-reference, can be converted fairly easily (I hope -- the
examples below make some headway here) into a formal semantics, with the
(intentionally) undefined notion of `immediate representation' of values
left as one of its parameters.  The use of terms such as `location' is
therefore intended to be /indicative/ of common machine architectures,
but not exclusively tied to them, since much more abstract systems --
such as, for example, denotational semantics -- can serve just as well.

> All we know is that it maps the name 'x' to the object 23
> somehow. Maybe there's no persistent storage, and the dict stores
> instructions telling the VM how to recreate the object 23 when it is
> needed. Who knows? But at this explanatory level, there are no
> locatives involved. Names and objects float as disembodied entities in
> the aether, and dicts map one to the other.

Explain the list `l' above using this model.

> At an even lower explanatory level, the CPython implementation of
> dictionaries works by storing a pointer (or if you prefer, a
> reference) to the object in a hash table. Pointers, of course, are
> locatives, and so finally we come to the explanation you prefer.

No!  No, no, no!  I want to keep locatives /out/ of the picture, because
Python doesn't provide them.

> I daresay you probably get annoyed at me when I bring up explanations
> at the level of copying bytes. You probably feel that for the average
> Python programmer, *most of the time* such explanations are more
> obfuscatory than useful.

My objective is to explain defined semantics: behaviour common to all
Python implementations (real, or imagined).

> Of course, there are exceptions, such as explaining why repeated
> string concatenation is likely to be slow. There is a time and a place
> for such low level explanations, but not at the high-level overview
> needed by the average Python programmer.

Right: performance is inherently related to implementation details and
understanding performance requires an understanding of the
implementation behaviour.

But that's very different from understanding language semantics, which
is (I hope) the topic under discussion.  

> And you would be right. But I argue that your explanation at the level
> of references is exactly the same: it is too low level. It relies on
> specific details which may not even be true for all implementations of
> Python.

No.  It introduces concepts which are analogous to the way some
implementations behave.  But as a model for Python, it works for all
implementations.

> It certainly relies on details which won't be true for hypothetical
> versions of Python running on exotic hardware.

If they produce other results for my examples above, then they don't
implement Python.

> If that's too bizarre for you, think about simulating a Python VM in
> your own head. If we know one thing about the human brain, it is that
> thoughts and concepts are not stored in single, well-defined
> locations, so when you think of "x=23", there is no pointer to a
> location in your head.

But if I'm implementing Python in my mind, I need to invent a concept of
location -- or something isomorphic -- or I'll get the semantics wrong.
Indeed, this seems to be exactly what's happening to confused beginners
-- they're getting the semantics wrong in their minds, so the real
implementation doesn't match their expectations.

> > No, that's my point: Python programmers /don't/ have direct access to
> > objects.  The objects themselves are kept at arm's length by the
> > indirection layer of references.
> 
> I think you are wrong. If I want a name 'x' to refers to (is bound to) 
> the object 23, I write x=23, not some variation of:
> 
> create object 23
> give me a reference to that object
> bind the reference to name 'x'
> 
> Those three steps may take places at some level of the Python VM, but 
> that's not what *I* do as a Python programmer.

No.  You type; the VM obeys.  If you want to understand its behaviour,
you must 

> >   * A /value/ is an item of data.  The range and nature of values is
> >     language specific.  Typically, values encompass at least some kinds
> >     of numbers, textual data, and compound data structures; they may
> >     also include behavioural items such as functions.
> 
> Yes. This is an intuitive meaning of the world value. In Python, all 
> values are objects. Some typical examples of values are:
> 
> 5, None, "Fred", True, 3.5, [2, 3, 4], {}, lambda x: x+1
> 
> These (and more complicated structures built on top of them) are the 
> things of interest to the programmer. They are the values: the things 
> which are denoted by the symbols '5', 'None', '"Fred"' etc.

Good: agreement.  We're making progress.

> >   * A /location/[1] is an area of memory suitable for storing the
> >     /immediate representation/ (which I shall abbreviate to /IR/) of a
> >     value.  (A location may be capable of storing things other than IRs,
> >     e.g., representations of unevaluated expressions in lazily evaluated
> >     languages.  Locations may vary in size, e.g., in order to be capable
> >     of storing different types of IRs.)
> 
> At the level of Python code, we have no access to such locations. The
> closest we have is the id() function, which uses location in memory as
> a unique ID for objects, but this is an accident of the CPython
> implementation. Whatever the /immediate representation/ of a value is,
> we can't manipulate it directly in Python code.

Indeed we have not.  But an understanding of their existence is
nonetheless essential in order to comprehend Python semantics.  Indeed,
it's /because/ the locations are not directly visible to a Python
programmer that we can describe them in such abstract terms; but
omitting them from the explanation altogether leaves important aspects
of behaviour unexplained.

> >   * A /variable/ is a location to which has been /bound/ a name.  Given
> >     an occurrence of a name in a program's source, there is a language
> >     specific rule for determining the variable to which it is bound.
> 
> According to this definition, there are no variables in Python, because 
> Python's data model is that names are an abstract mapping between symbols 
> and values, not between symbols and locations.

Chapter and verse?  Besides, `names are an abstract mapping'?  Names are
names, dammit!  Names, symbols, identifiers: all mean the same thing.

And again, I challenge you to explain the Python 3 interaction above
without the concept of location.

> >   * /Evaluation/ is the process of determining a value from an
> >     expression.  The /value of/ an expression is the result of
> >     evaluating the expression.  This value is, in general, dependent on
> >     the contents of the locations to which names appearing in the
> >     expression are bound.
> [...]
> > The argument passing model `pass-by-value' has a number of distinctive
> > properties.
> > 
> >   * The argument expression is fully evaluated before the function is
> >     called, yielding an argument value.
> > 
> >   * The corresponding parameter name is bound to a fresh location.
> > 
> >   * The argument value IR is stored in the parameter's location.
> 
> This is an underspecified definition. Without a definition of
> /immediate representation/, we can't determine what this means.

It is intentionally underspecified, since the immediate representation
of values varies between languages, and the whole point was to define
the argument passing models in a language-independent manner.

> I can guess that, based on Pascal, Fortran and C, the /immediate
> representation/ of a value is whatever data structure represents that
> value. However, I fear that you are going to try to slip in an
> open-ended definition, that /immediate representation/ could be
> *anything* -- for ints in C, it will be the bytes that represent the
> int; for C arrays, it will be a pointer to the bytes that represent
> the array; for Python, it will be references to objects; for Algol 60,
> it will be thunks.

No slipping involved.  The definition is quite intentionally open-ended,
and if I didn't make it explicit then I should have done.

C arrays are strange: it is simply impossible for an array to be the
value of a C expression (except in the very strange case of the operand
of `sizeof'); therefore I don't need to define an IR for arrays to get
pass-by-value to work: the fact that argument expressions are evaluated
eliminates all arrays at the first step.

Regarding Algol, no, you can't catch me out that easily.  A thunk in
Algol represents an /unevaluated expression/.  But I've only made use of
immediate representation of /values/ -- i.e., the result of fully
evaluating an expression.

I should have made this explicit: immediate representations are the
`common currency' for doing expression evaluation.  Expressions are
(typically) constructed from subexpressions: to evaluate, one gathers
the values of the subexpressions -- recursively if necessary -- and
combines them in the appropriate manner to discover the final value.  We
do this in fact by manipulating immediate representations.

One kind of subexpression is typically an identifier: this is an
interesting case, so I'll talk about it in detail.  To evaluate an
identifier, we find the location bound to that identifier, and extract
the IR from the location.

We've got a lot of types, processes and mappings.  Let's make it clear
what's going on.

Types:
  N: names
  V: values
  R: immediate representations
  L: locations
  X: expressions

  E = N -> L: environments
  S = L -> R: stores

Operations:
  rep: V -> R                 value to immediate-representation
  unrep: R -> V               immediate-representation to value

  eval: (X, E, S) -> (R, S)   evaluation
  alloc: () -> L              yields a fresh location

(A `store' maintains the mapping between locations and their contents.
It's an input to evaluation because one needs to retrieve the contents
of variables; it's an output because evaluating some expressions may
have side effects on storage.)

Names are a kind of expression.  The operation of retrieving a variable
looks like this:

  eval(n, e, s) = (s(e(n)), s)

That is, we look the name up in the environment to find a location, and
we find the contents of the location in the store; the store is
unchanged by this operation.

Assignment to a variable is straightforward in this model:

  assgn(n, e, s, r) = (?, s[r/e(n)])

For the unfamiliar: x[y/z] is a function which behaves like x, except
that it returns y on input z: i.e.,

                y     if a = z
  x[y/z](a) = {
                x(a)  if a /= z

That is, the /value/ of assignment is language-specific (and it may not
syntactically be an expression, but statements can be modelled as
expressions which yield unspecified values which are unavailable to the
program due to syntactic constraints, and this saves us having to
introduce another kind of thing).

Note that we don't actually need the rep/unrep operations in order to
define the semantics, at least until we actually start constructing
values.

> To avoid weakening pass-by-value to mean everything and anything at
> all, I'm going to say that the /immediate representation/ is the bytes
> which represent the value.

And this decision takes you back round another loop of the argument and
leads you to all manner of difficulty.  No, the right answer is
necessarily language-specific.  Besides, I never mentioned bytes when
discussing values, and certainly the idea that there might be a unique
representation in terms of bytes is completely wrong.

I hope I've already shown that my definition of pass-by-value is /not/
so weak as to allow anything at all -- in particular, it doesn't have
the same semantics as pass-by-reference or pass-by-name.

To this end, let's formalize it some more.  Suppose we have an argument
expression x, which is part of a call to a function f; x is to be
evaluated in environment e, with store s.

We first evaluate the argument:

  (r, s') = eval(x, e, s)

We now allocate a fresh location for the corresponding parameter

  l = alloc()

and bind the parameter name -- call it n -- in the function's
environment e':

  e'' = e'[l/n]

Finally, we store the argument value representation in the location:

  s'' = s'[r/l]

We do this for all the pass-by-value arguments (it doesn't matter
whether we do the parameter stores all at once later or interleaved,
because the locations are distinct from all previous locations),
augmenting the environment e''.  We evaluate the function body with
respect to the final store and this augmented environment.

This model is now sufficiently detailed that we can /prove/ that
implementing `swap' using pass-by-value (as defined here) is impossible.

The soundbite version of all of this: pass-by-value means that the
parameters are distinct variables, which are initialized by assignment.

> (That is, the value itself.)

No!  Be very careful here.  Do not confuse the value with its
representation: to do so is to fall into a very nasty trap.  A sequence
of bytes is not a number, for example, though one may define mappings
between the two.

> > By contrast, the `pass-by-reference' model has other distinguishing
> > properties.
> > 
> >   * Whether arbitrary argument expressions are permitted is language
> >     dependent; often, only a subset of available expressions -- those
> >     that designate locations -- are permitted.
> 
> As you said above: "The /value of/ an expression is the result of 
> evaluating the expression". Given the expression 2+3, the result of that 
> expression is 5, not the location where 5 is stored.

Right...

> There is no reason to believe that 5 designates a location, as opposed
> to designating the number of peas in a pod or the average length of a
> piece of string.

And indeed I don't think that 5 does designate a location.  But
fortunately my definition wasn't talking about /values/ designating
locations: it was talking about /expressions/ designating locations, and
that's entirely different.  Considered as expressions, `2 + 3' does not
equal `5' -- one contains an addition operator and two subexpressions,
each an integer constant, whereas the other is a single constant.  The
two should produce the same result on evaluation, but that's a different
matter.

> For want of a better description, let me re-word the above to say:
> 
> * Whether arbitrary argument expressions are permitted is language
>   dependent; often, only a subset of available expressions -- e.g. those
>   that evaluate at a named location -- are permitted.
> 
> Note that I say they evaluate *at*, not *to*, a fixed location.

Yes, I noticed that, but I don't know what it means.

> A practical example, to ensure we're talking about the same thing. In 
> Pascal, I can declare a procedure swap(a, b) taking two VAR parameters, 
> which use call-by-reference semantics. I might do something like this:
> 
> a := 8;  { number of peas in a pod }
> b := 13;  { a baker's dozen }
> swap(a, b);
> 
> Even though the values of a and b do not designate locations, the
> compiler can pass them to the procedure because the named variables a
> and b exist *at* particular locations.

The values of `a' and `b' aren't important here.  What is important is
that the /expression/ `a' designates a location, namely the location
bound to the name `a'; similarly for `b'.

> Contrast this with:
> 
> swap(a, 10+3);

And `10 + 3' does not designate a location.

(The circumlocution involved with `designating locations' is to cope
with expressions like `v[5]' as well as simple names.  In some
languages, an expression which designates a location is said to produce
an `lvalue' rather than an `rvalue'; we can think of an `lvalue' as
being a location and an `rvalue' as being an immediate representation:
we can convert the former to the latter by consulting the store; but we
cannot convert in the other direction without allocating.)

> which will fail in Pascal, because the value of the expression 10+3 
> doesn't correspond to a named location. (Presumably this is a design 
> choice, because the value of the expression will certainly exist at a 
> known location, although possibly not known until runtime.)

I think the design choice is that it's not considered meaningful to
modify the value of `10 + 3', whereas it is considered meaningful to
modify the value of `a'.  (In both cases, by `the value of FOO', I mean
`the result of evaluating the expression FOO'.)

> >     If the argument expression does designate a location, then this
> >	location is the /argument location/.
> 
> Replace this with "If the argument expression does evaluate at an
> allowed location (named location for Pascal), then..." and I will
> accept it.

I still don't know what `evaluate at' means.

> >   * The corresponding parameter name is bound to the argument location.
> 
> According to this definition, Python is call-by-reference. Refer my
> code snippet above.

No.  Work through it again, now I've explained what's going on more
clearly.

Here's the more formal version.  We need some new notions.

Types:
  D: expressions which designate locations

Operations:
  loc: (D, E, S) -> (L, S)    location designated by expression

Important: D is a subset of X; if

  (l, s') = loc(d, e, s)

then (axiomatically)

  (s'(l), s') = eval(d, e, s)

If n is a name, then

  loc(n, e, s) = (e(n), s)

(and we can see that this definition is consistent with the one above).

So, pass by reference.  Let x be the argument expression, e the caller's
environment, and s the prevailing store.  Firstly, if x in D then

  (l, s'') = loc(x, e, s)

Otherwise (if supported),

  (r, s') = eval(x, e, s)
  l = alloc()
  s'' = s'[r/l]

(Note that, after this, we always have (s''(l), s'') = eval(x, e, s).)

If n is the corresponding parameter name, and e' is the function's
enclosing environment, then all we need to complete the job is

  e'' = e'[l/n]

The significant difference between the two is that, in pass-by-value,
the parameter names are /always/ bound to fresh locations, whereas in
pass-by-reference this need not be the case.

The soundbite version of all of this: pass-by-reference means that new
variables are not constructed; rather, new names are given to the
caller's variables.

> But clearly Python doesn't behave like call-by-reference in other
> languages: you can't write a swap() procedure.

The definition I've given for call-by-reference should make it possible
to write a `swap'.  Indeed, `can I write swap?' is a good rule-of-thumb
test for call-by-reference -- though it may also detect call-by-name and
more exotic things.

> This is where I quote Barbara Liskov, talking about the language CLU 
> which has precisely the same calling semantics as Python:
> 
> "In particular it is not call by value because mutations of arguments
> performed by the called routine will be visible to the caller. And it
> is not call by reference because access is not given to the variables
> of the caller, but merely to certain objects."

Quite why she decided that CLU isn't call-by-value, despite it having
the same semantics as Lisp which certainly is call-by-value and has been
so since 1958 (and may even have introduced the concept!) is a mystery
to me.

> Once you start declaring that a language is "pass-by-value, where the
> value is a Foo rather than the actual value", pass-by-value can be
> used to describe *anything*. Pass- by-reference becomes pass-by-value
> where the value is the location of the value.

No, it can't.  Or prove your claim: provide a definition of `immediate
representation' which allows one to implement `swap' using call-by-value
semantics.

> Pass-by-object is pass-by-value where the value is a reference to the
> object (your claim).

I don't believe in `pass-by-object' as being a different thing, no.

> > Even for those.  C doesn't pass arrays at all; instead it passes
> > (programmer-visible) pointers.  See other article.
> 
> But you are conflating concepts again. The value of an array is the 
> array: it's what the programmer asked for when he declared an array. See 
> your own definition of value above: "A /value/ is an item of data."

Again: in C, the value of an expression cannot be an array (6.3.2.1p3).
The definition of pass-by-value requires that the argument expression be
evaluated.  Therefore, the resulting argument value cannot be an array;
it is, instead, a pointer, and that pointer is passed by value.

I'll quote 6.3.2.1p3 in full:

: [#3] Except when it is the operand of the sizeof operator or the unary
: & operator, or is a string literal used to initialize an array, an
: expression that has type ``array of type'' is converted to an
: expression with type ``pointer to type'' that points to the initial
: element of the array object and is not an lvalue. If the array object
: has register storage class, the behavior is undefined.

This happens logically before function calling: there is a sequence
point before the call (6.5.2.2p10).  Note that 6.5.2.2 (which defines
the behaviour of the function call operator) does not mention arrays at
all.  Rather, it describes conversion of arguments, as if by assignment,
for function types with and without prototypes, and for variadic
functions, and then states:

: [#8] No other conversions are performed implicitly; in particular, the
: number and types of arguments are not compared with those of the
: parameters in a function definition that does not include a function
: prototype declarator.

(6.5.16, which describes assignment, doesn't mention arrays either.)

> You get pass-by-value semantics: when you pass an array to foo, the 
> entire array is duplicated. Changes to x are not visible in the caller's 
> array. C arrays do not behave like this with an equivalent
> declaration.

But that's because of the (bizarre) way that arrays are treated during
expression evaluation.

> Change the declaration to be VAR x, and using pass-by-reference 
> semantics, and the array is *not* duplicated, and changes to x *are* 
> visible to the caller. C's default handling of arrays is just like 
> Pascal's call-by-reference semantics, not like pass-by-value. This is 
> AFAIK unique in C to arrays.

The effect is similar from the point of view of the caller; the cause is
very different, as is the whole situation from the point of view of the
called function, which is left with an explicit pointer.

> > And this is entirely due to the difference in their immediate
> > representations of values.
> 
> Values are values. Regardless of whether you are using C or Pascal or
> Python, the value of 1+1 is 2, not some arbitrary memory location. I'm
> going to quote from Fredrik Lundh:
> 
> "I'm not aware of any language where a reference to an object, rather 
> than the *contents* of the object, is seen as the object's actual value. 
> It's definitely not true for Python, at least."

And that's why I've been at such pains to distinguish the /immediate
representation/ of the value from the value itself.

> The viewpoint that values are references is bizarre and counter-
> intuitive, and it leaves us with no simple way of talking about the value 
> of expressions in the sense that 2 is the value of the expression 1+1.

Unless one introduces the necessary concept of representation.
Fortunately I did that.

-- [mdw]



More information about the Python-list mailing list