Programming intro book ch1 and ch2 (Windows/Python 3) - Request ForComments

A

Alf P. Steinbach

I finally finished (draft), I believe!, chapter 2...

Chapter 1 gets the reader up & running, i.e. it's "Hello, world!", basic tool
usage, without discussing anything about programming really. One reaction to
this chapter, based on the two example programs in it, was that it wasn't
gradual and slow enough; hey, those examples are far too advanced, and
unexplained! But that reader misunderstood: the progression is actually *slower*
than one might expect. This chapter is only about tool usage. I.e., it's about
getting those programs running, for the reader who can't rely on teachers or
fellow students or, as Kernighan & Ritchie put it, "your local guru" (IIRC).

Chapter 2 is about Basic Concepts (of programming). It's the usual: variables,
basic types and arrays, loops, decision, routines, classes, events, although not
presented in that order. I make heavy use of complete, concrete examples, many
of them graphical, and everything is in support of what's actually needed for
such concrete examples. The intent is to enable the reader to experiment and try
things out -- since the only way to really learn is by doing! As best I could
I've labored to apply this minimalism also to the Python language, using only a
"minimal" subset (to the degree of not even introducing boolean ops :) ).

Chapter 3 will, by my current plan, delve into the Python language and such
things as how integers and floating point works on the inside, and that includes
those in chapter 2 not even mentioned boolean operations. One important issue,
introducing exceptions, and in support of that, type hierarchies. After chapter
3 I have only much vaguer notions about what to introduce in what order, but a
main issue, assuming that I go on with this writing, will be to apply and teach
methodology all the way, integrated into the examples and text.

A table of contents + chapters 1 and 2 is available in PDF format at Google Docs, at

<url: http://tinyurl.com/programmingbookP3>

Comments are very welcome!

Re comments: there are two deviations from current Python practice in chapter 2.
First, that I use spaces inside argument parentheses, which makes the code more
readable when one gets used/trained to it because it gives the eye more direct
information (with some training visual structure can be processed unconsciously
& effortlessly, but purely logical structure has to be processed analytically).
The second deviation is that since most names are constants, I do not follow PEP
8's recommendation to use uppercase names of constants. In fact almost no Python
code does, but then it seems that people are not aware of how many of their
names are constants and think that they're uppercasing constants when in fact
they're not. E.g. routine arguments and in particular routine names are usually
constants, absolutely not meant to be modified, but it would be silly to UC...

So both these two deviations from Python practice are /intentional/, since this
is a book about programming, not about Python the language & how to conform to
idiosyncratic language-specific conventions.

But, if there are other deviations from Python practice I'd be very glad to hear
of it! I'm very hopeful that any such convention deviations can be fixed. :)


Cheers,

- Alf
 
M

Mensanator

The second deviation is that since most names are constants,

Really? Does that mean you don't use literals, to save the time
required to convert them to integers? Isn't that done at compile
time?

So, instead of doing the Collatz Conjecture as

while a>1:
f = gmpy.scan1(a,0)
if f>0:
a = a >> f
else:
a = a*3 + 1

You would do this?

zed = 0
one = 1
two = 2
twe = 3
while a>one:
f = gmpy.scan1(a,zed)
if f>zed:
a = a >> f
else:
a = a*twe + one

Does this really save any time?

Now, it's a different story if you're using the gmpy module.
You DON'T want to use literals in loops involving gmpy, because
they would have to be coerced to .mpz's on every pass through the
loop.

In that case, you DO want to use constants as opposed to literals:

ZED = gmpy.mpz(0)
ONE = gmpy.mpz(1)
TWO = gmpy.mpz(2)
TWE = gmpy.mpz(3)
while a>ONE:
f = gmpy.scan1(a,0) # int used here, so it can be a literal
if f>ZED:
a = a >> f
else:
a = a*TWE + ONE

And yes, the time savings can be tremendous, especially when 'a'
has over 50,000 decimal digits.

.. I do not follow PEP
8's recommendation to use uppercase names of constants. In fact almost no Python
code does,

Mine does when I use gmpy. Otherwise, the notion that "most names
are constants" is generally false.
 
A

Alf P. Steinbach

* Mensanator:
Really? Does that mean you don't use literals, to save the time
required to convert them to integers? Isn't that done at compile
time?

So, instead of doing the Collatz Conjecture as

while a>1:
f = gmpy.scan1(a,0)
if f>0:
a = a >> f
else:
a = a*3 + 1

You would do this?

zed = 0
one = 1
two = 2
twe = 3
while a>one:
f = gmpy.scan1(a,zed)
if f>zed:
a = a >> f
else:
a = a*twe + one

That seems to have no relation to what you quoted / responded to.

On the other hand, if there is some specific rôle played by the 3 above, where
some other value (like e.g. 5) might be used instead, then a self descriptive
name for that rôle might be good.

Such reasonable naming (not what you did above) then allows easier modification
of and makes it easier to understand the code.

That said, and a bit off-tangent to your comment's main thrust, the time spent
on coding that repeated-division-by-2 optimization would, I think, be better
spent googling "Collatz Conjecture" -- avoiding writing /any/ code. ;-)

Does this really save any time?

If by "it" you mean the silly naming, no it doesn't.

On the contrary, it wastes time, both for writing the code and reading it.

Generally, IMO, think about the clarity of your code. If naming something
increases clarity, then name the thing. If it doesn't increase clarity, don't.

Now, it's a different story if you're using the gmpy module.
You DON'T want to use literals in loops involving gmpy, because
they would have to be coerced to .mpz's on every pass through the
loop.

In that case, you DO want to use constants as opposed to literals:

ZED = gmpy.mpz(0)
ONE = gmpy.mpz(1)
TWO = gmpy.mpz(2)
TWE = gmpy.mpz(3)
while a>ONE:
f = gmpy.scan1(a,0) # int used here, so it can be a literal
if f>ZED:
a = a >> f
else:
a = a*TWE + ONE

And yes, the time savings can be tremendous, especially when 'a'
has over 50,000 decimal digits.

Yeah, good point. Few languages have compile time evaluation of logically
constant expressions. C++0x will have that feature (called 'constexpr' IIRC) but
in Python, current C++ etc. it's just a good idea to precompute values, and name
them, rather than computing them again and again where they're needed.

. I do not follow PEP

Mine does when I use gmpy. Otherwise, the notion that "most names
are constants" is generally false.

No, it depends on what you mean by "constant". The problem with Python, as
Google noted, is that the language is so excessively dynamic: even names of
routines are variables, and there /are/ no named user defined constants except
logically, in the programmer's mind. And logically (that is, at the "in the
programmer's mind" level), if you define "constant" as a name whose value will
not change after initialization, then routine names are constants.

However, if you define "constant" as only a global scope (that is, module scope)
name that denotes a boolean, numerical or string or Null value and that doesn't
change after initialization, then your statement about the scarcity of constants
appears to be true, but using a practically useless definition.

I think for such constants exported by modules it's a good idea to at least
provide uppercase names so as conform to very firmly established convention.
There might even be tools that rely on that convention. But for application code
the uppercase names are just distracting, and they don't help you...


Cheers & hth.,

- Alf
 
S

Steven D'Aprano

In fact almost no Python
code does, but then it seems that people are not aware of how many of
their names are constants and think that they're uppercasing constants
when in fact they're not. E.g. routine arguments

Routine arguments are almost never constants, since by definition they
will vary according to the value passed by the caller.

(The exception is that some formal parameters in Python are given default
values and flagged as "do not use", e.g. some methods in the random
module. One might argue that they are constants in the sense that the
caller is warned not to use them at all.)

and in particular
routine NAMES are usually constants, absolutely not meant to be
modified, but it would be silly to UC...
[emphasis added by me]


The only thing sillier than uppercasing routine names is to confuse the
name of a thing for the thing it represents. The names of *all* entities,
whether of constants, variables or routines, are "not meant to be
modified". As a general rule, programs will no more work if you rename a
variable 'x' to 'y' than if you rename a function 'f' to 'g' -- that's
why refactoring requires that any renamings be done carefully. There's no
need to single out routines for special treatment in that regard.

As far as I know, no programming language provides a standard facility
for renaming entities, be they data or routines: the closest we have is
something like Python where you can do this:

# make an entity x
x = 23
# use it
x += 1
# make a new name that refers to the same entity as x
y = x
# delete the old name
del x
# now use the new name
y += 1

So while it is true that routine names are not meant to be modified,
neither are variable names (that is, the names of variables) or the names
of anything else either. But this is not what is meant by "constant":
being a constant means that the *value*, not the *name*, is never meant
to change.

It is true that, usually, the names of certain types of object (usually
functions, classes, methods and modules) are typically expected to not be
re-bound. Having done this:

def parrot(colour='blue'):
return "Norwegian %s" % colour.titlecase()


I would *rarely* rebind the name parrot to another object. Rarely, but
not quite never: I might monkey-patch the function, or decorate it in
some way, or change the implementation, so such routines aren't quite
constant in the sense you mean. While it's unusual to vary a function, it
isn't forbidden.

Even in languages where routines are "first class objects" like ints and
strings, we treat such objects as special. Even if the function named
parrot above was expected to never change, I wouldn't describe it as a
constant. "Constant" and "variable" refer to *data*, not routines or
other special objects like modules.

It's illustrative to consider what we might do when writing a program
that does treat functions as data, say, a program that integrated other
functions. One might very well do something like this:

function_to_be_integrated = math.sin # a variable
SIMPSONS = integrators.simpsons_method # a constant
UPPER_RECT = monkey_patch(integrators.upper)
LOWER_RECT = monkey_patch(integrators.lower)

integrate(function_to_be_integrated,
limits=(-math.pi/2, math.pi),
method=SIMPSONS
)

This would clearly indicate that `function_to_be_integrated` holds
variable data (which happens to be a function) while `SIMPSONS` etc are
expected to hold constant data (which also happen to be functions).
 
S

Steven D'Aprano

That said, and a bit off-tangent to your comment's main thrust, the time
spent on coding that repeated-division-by-2 optimization would, I think,
be better spent googling "Collatz Conjecture" -- avoiding writing
/any/ code. ;-)

That's a strange thing to say.

Now, it's a different story if you're using the gmpy module. You DON'T
want to use literals in loops involving gmpy, because they would have
to be coerced to .mpz's on every pass through the loop.
[...]
Yeah, good point. Few languages have compile time evaluation of
logically constant expressions.

Surely that's an implementation issue rather than a language issue.

C++0x will have that feature (called
'constexpr' IIRC) but in Python, current C++ etc. it's just a good idea
to precompute values, and name them, rather than computing them again
and again where they're needed.

CPython 2.5 and on has a keyhole optimizer that replaces many constant
expressions with pre-computed values.

# Python 2.4 0 0 LOAD_CONST 0 (1)
3 LOAD_CONST 0 (1)
6 BINARY_ADD
7 RETURN_VALUE

# Python 2.5 1 0 LOAD_CONST 1 (2)
3 RETURN_VALUE


Unfortunately it doesn't help Mensanator's case, because there's no way
to tell the compiler to generate mpz objects instead of int objects, and
expressions such as gmpy.mpz(0) are not recognised as compile-time
constants.

No, it depends on what you mean by "constant".

All names are constant. Always. The Python language does not support
renaming names -- as far as I know, no language does.

The problem with Python,
as Google noted, is that the language is so excessively dynamic: even
names of routines are variables,

Don't say that, that is confusing the name with the value. The terms
"constant" and "variable" refer to the values bound to a name, not the
name itself: what you mean is that even routines are variables.

Consider the following Pascal declaration:

const
a = 1;
var
b: integer;

Neither name 'a' nor 'b' ever change; a is always a and b is always b.
You wouldn't describe b as a constant because the name never changes,
would you? What matters is that the value assigned to the name can, or
can't, be changed by the caller.

Like any other language, Python *names* are always constant: you can
create them at will (without needing a declaration); you can delete them
(with the del statement, something Pascal doesn't allow you to do); but
there's no way to change a name once it exists. Obviously it would be
misleading to claim that Python name/object bindings ("variables") are
therefore constant because of this.

So how would I say what you are trying to say, but in a less-misleading
fashion?

Names can always (well, almost -- there are a few exceptions) be rebound,
regardless of whether they are currently bound to a data object (string,
int, float...) or a routine (function, method...). Since the name by
which we refer to a function can be rebound, we refer to the name/object
binding as variable rather than constant -- exactly the same as any other
name/object binding.

Or the shorter way:

Since all names can be rebound, regardless of what value they have (int,
string, function, whatever) all names are variables and Python has no
constants other than special pre-defined names like None.


and there /are/ no named user defined
constants except logically, in the programmer's mind.

Yes, that is correct, although there are tricks you can do to make
slightly-more-constant-like constants, such as Alex Martelli's "constant
module" recipe in the Cookbook, but there are no true constants in Python.

And logically
(that is, at the "in the programmer's mind" level), if you define
"constant" as a name whose value will not change after initialization,
then routine names are constants.

Some languages enforce that, and in those languages, it makes sense to
describe functions as constants. Python is not one of those languages.

However, if you define "constant" as only a global scope (that is,
module scope) name that denotes a boolean, numerical or string or Null
value and that doesn't change after initialization, then your statement
about the scarcity of constants appears to be true, but using a
practically useless definition.

I won't speak for Mensanator, but I wouldn't be so restrictive about the
*types* of data that constants can hold. Not in Python, at least, other
languages can and do limit the types of constants to a handful of
predefined types known to the compiler.

Nor would I say that "constants" (note the scare-quotes) can only occur
in module scope. There's nothing wrong with naming "constants" in other
scopes, although I must admit I'm a tad less consistent about doing so
than I should.
 
A

Alf P. Steinbach

* Steven D'Aprano:
Routine arguments are almost never constants, since by definition they
will vary according to the value passed by the caller.

I'm sorry, but that requires a definition of "constant" that explicitly excludes
routine arguments, which is like saying horses are not mammals, just "because".

There are two sensible definitions of "constant": compile time constant, and
constant-after-initialization.

And since Python doesn't have or support user defined compile time (named)
constants even in the way that a programmer views the execution of a program,
only the const after initialization meaning can /reasonably/ apply.

I hope you see that with the constant-after-initialization meaning it's rather
irrelevant that an argument's value "will vary according to [the actual
argument]" -- for an argument A, id(A) does in general not vary after
initialization, after creation of A; and it can certainly not vary before!

Consider some C++ code:

void foo( SomeType const v )
{
// Here the name v is constant: that name's value can't change.
// (Except that in C++ you can do anything by using low-level stuff.)
}

As the comment there exemplifies, in addition to the 2 main meanings of
constness one can differentiate between constness at different levels of an
expression, e.g., in Python, with respect to what the language enforces,

s = "blah"

is a non-constant name referring to a constant (immutable value), while

a = ["blah"]

is a non-constant name referring to a non-constant value (a Python 'list')
containing a constant value -- but with respect to intended constness there
might be more constness here, although there can't be less.

Since Python doesn't support constness enforcement, your statement that routine
arguments are almost never constants must refer to their actual usage, i.e.
intention. It may be the case that in most code you're familiar with routine
arguments are generally modified. And/or it may be that in code you're familiar
with routines regularly modify the objecs that their arguments refer to, i.e.
that the arguments are constant names referring to mutable objects.

In code that I'm familiar with, but that's admittedly mostly in other languages,
most argument names are constant at the top level of constness, i.e., translated
to Python, id(A) does generally not change when A is a formal argument.

(The exception is that some formal parameters in Python are given default
values and flagged as "do not use", e.g. some methods in the random
module. One might argue that they are constants in the sense that the
caller is warned not to use them at all.)
Huh.

and in particular
routine NAMES are usually constants, absolutely not meant to be
modified, but it would be silly to UC...
[emphasis added by me]


The only thing sillier than uppercasing routine names is to confuse the
name of a thing for the thing it represents. The names of *all* entities,
whether of constants, variables or routines, are "not meant to be
modified". As a general rule, programs will no more work if you rename a
variable 'x' to 'y' than if you rename a function 'f' to 'g' -- that's
why refactoring requires that any renamings be done carefully. There's no
need to single out routines for special treatment in that regard.

To be pedantic, original routine names are usually absolutely not meant to be
assigned to.

If you have

def foo(): whatever()

you simply shouldn't, in general, do

foo = bar

I think you understood that, i.e., that the above comment of yours is just rhetoric?

As far as I know, no programming language provides a standard facility
for renaming entities, be they data or routines:

Eiffel (IIRC) and C++ come pretty close.

E.g., in C++:

int a;
int& b = a; // A new name for a.

b = 123; // Assigns to a.

the closest we have is
something like Python where you can do this:

# make an entity x
x = 23
# use it
x += 1
# make a new name that refers to the same entity as x
y = x
# delete the old name
del x
# now use the new name
y += 1

Well, you're off on the wrong track as far as convincing me about something is
concerned. First, your belief about renaming not being supported by any
languages is largely incorrect, as shown above. Secondly, I was not talking
about renaming things -- that creative interpretation is pretty meaningless...


[snipped the rest, irrelevant]

Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* Steven D'Aprano:
That's a strange thing to say.

No. The code shown was like attacking Fermat's last theorem with a little Python
script checking out number triplets. It's already been done (not to mention that
that theorem's been proven, although that's, AFAIK, not the case for Collatz').

Now, it's a different story if you're using the gmpy module. You DON'T
want to use literals in loops involving gmpy, because they would have
to be coerced to .mpz's on every pass through the loop.
[...]
Yeah, good point. Few languages have compile time evaluation of
logically constant expressions.

Surely that's an implementation issue rather than a language issue.

No, it isn't.

An optimizer can only do so much, as you yourself note below!

With language support it's a very different matter because guarantees propagate
so that sophisticated analysis is no longer necessary: the compiler /knows/,
because it's explicitly being told.

CPython 2.5 and on has a keyhole optimizer that replaces many constant
expressions with pre-computed values.

# Python 2.4
0 0 LOAD_CONST 0 (1)
3 LOAD_CONST 0 (1)
6 BINARY_ADD
7 RETURN_VALUE

# Python 2.5
1 0 LOAD_CONST 1 (2)
3 RETURN_VALUE


Unfortunately it doesn't help Mensanator's case, because there's no way
to tell the compiler to generate mpz objects instead of int objects, and
expressions such as gmpy.mpz(0) are not recognised as compile-time
constants.
See?

;-)



All names are constant. Always. The Python language does not support
renaming names -- as far as I know, no language does.

No-ones been talking about renaming names. I think that's purely rhetorical on
your part but it may be that you really believe so. In the latter case, just try
to interpret statements so that they're meaningful instead of meaningless. :)

Don't say that, that is confusing the name with the value.
Nope.


The terms
"constant" and "variable" refer to the values bound to a name, not the
name itself:

I'm sorry, that's incorrect.

Quote from §4.1 "Naming and binding" of the Python 3.1.1 language spec:

"If a name is bound in a block, it is a local variable of that block, unless
declared as nonlocal. If a name is bound at the module level, it is a global
variable. (The variables of the module code block are local and global.) If a
variable is used in a code block but not defined there, it is a free variable."

what you mean is that even routines are variables.

I'm sorry but I can't make sense of what you write here. In addition I'm not
sure what you mean because there are two main interpretations.

If you mean that user defined Python routines are mutable, yes that's right, but
not what I was talking about (which you can easily see by checking whether it
makes sense in context; it doesn't).

If you mean that a routine of itself is a variable, no it isn't, but the name of
a routine, like "foo" in

def foo: print( "uh" )

or "bar" in

bar = lambda: print( "oh" )

is a variable, per the language specification's definition quoted above (and
also by any reasonable meaning of "variable"!).

The meaning of "variable" for Python terminology is specified by the language
specification, as quoted above: a variable is a name.


[snipped rest, irrelevant]


Cheers & hth.,

- Alf
 
J

John Bokma

Steven D'Aprano said:
CPython 2.5 and on has a keyhole optimizer that replaces many constant
^^^^^^^
Shouldn't that be peephole?
expressions with pre-computed values.

And that's called constant folding.

Unless I misread your post (or have been out of touch with compiler
building too long)
 
S

Steven D'Aprano

Really? Does that mean you don't use literals, to save the time required
to convert them to integers? Isn't that done at compile time?

So, instead of doing the Collatz Conjecture as

while a>1:
f = gmpy.scan1(a,0)
if f>0:
a = a >> f
else:
a = a*3 + 1

You would do this?

zed = 0
one = 1
two = 2
twe = 3
while a>one:
f = gmpy.scan1(a,zed)
if f>zed:
a = a >> f
else:
a = a*twe + one

Does this really save any time?

There are some people who might argue that using *any* magic constants in
code is wrong, and that *all* such values should be declared as a
constant.

It's easy to take the mickey out of such an extreme position:

zed = 0 # in case we need to redefine 0 as something else
one = 1 # likewise
two = 3 # changed from 2 to 3 to reflect the end of the Mayan calendar

# The following is guaranteed to pass unless the world is ending.
assert one+one == two-zed
 
G

Gregory Ewing

Mensanator said:
Really? Does that mean you don't use literals, to save the time
required to convert them to integers?

I think all he means is that when he *does* use a named
constant, he spells it in lower case rather than upper
case, e.g. 'twopi' rather than 'TWOPI'.

I don't think there's anything much wrong with that. It
can be useful sometimes to visually distinguish constants
from variables, but it's not a necessity. Also the all-
uppercase convention isn't the only way to do that -- it's
a C-ism that isn't universally followed in Python. An
alternative often used is just to uppercase the first
character. Python itself uses that for many of its
built-in constants, such as None, True, False.

Arguing that functions are usually constants and should
therefore have uppercase names is missing the point --
everyone expects them to be constant anyway, so there's
no need for a typographical convention to indicate that.
In the rare cases where they're not constant, they can
usually be named in a way that makes this obvious.

(And BTW, looking up a global name is *slower* than using
a literal. Although a local name is probably about the
same speed as a literal, as they're both array accesses.)
 
S

Steven D'Aprano

* Steven D'Aprano:

I'm sorry, but that requires a definition of "constant" that explicitly
excludes routine arguments,
/s/explicitly/implicitly


which is like saying horses are not mammals, just "because".

No, it's like saying that horses are not apes, because the definition of
apes excludes horses.

But in any case, rather than continue my argument (which was absolutely
brilliant and flawless, I might add... *wink*), I'm going to skip ahead
to the place where the penny drops and I understand what you were trying,
but failed, to say.

Consider some C++ code:

void foo( SomeType const v )
{
// Here the name v is constant: that name's value can't change.
// (Except that in C++ you can do anything by using low-level
stuff.)
}

*penny drops*

Ahaha!!! Now I get it! You want to make the *parameter* of the function a
constant, rather than the *argument* passed to the function. In other
words, you want to prohibit something like this:

def f(x):
y = x + 1 # do something with the value passed by the caller
x = 0 # changing the binding will fail INSIDE the function

while still allowing the caller to call the function with variables.


Right -- now what you say makes sense, and is a perfectly reasonable
thing to do in languages that support constants.

You weren't clear about what you wanted, and the only thing I could think
of which matched your description was something completely bizarre: given
some function f with one parameter, you wanted to declare that parameter
as a constant with some value (say 42), so that calling the function with
an argument of any other value would be an error, e.g.:

x = 42
f(x) # succeeds
x = 43
f(x) # fails

E.g. implemented something like this:

def f(x):
if x != 42:
raise ConstantError('x is not the constant 42')
...

except that the test is done automatically by the compiler.

To be pedantic, original routine names are usually absolutely not meant
to be assigned to.

If you have

def foo(): whatever()

you simply shouldn't, in general, do

foo = bar

I think you understood that, i.e., that the above comment of yours is
just rhetoric?

First of all, I think you're underestimating the usefulness and frequency
of rebinding names to functions. I'll accept that it's uncommon, but it's
not *that* uncommon to justify "absolutely not meant to be assigned to".

In fact, it's so common that we have special syntax for one special case
of it. Instead of:

def f():
...

f = g(f)

we can write:

@g
def f():
...

While decorator syntax can only be used at function-definition time, the
concept of function decoration is far more general. You can decorate any
function, at any time, and doing so is very, very useful for (e.g.)
debugging, logging, monkey-patching, introspection, error-checking, and
others. And decoration itself is only one special case of function
rebinding.

As for your second point, my earlier comment is mostly aimed at what I
see as your misleading habit of referring to *names* as being constants,
rather than the value assigned to the name. Such terminology is
misleading. So if you want to call that rhetoric, I won't argue.


Eiffel (IIRC) and C++ come pretty close.

E.g., in C++:

int a;
int& b = a; // A new name for a.

b = 123; // Assigns to a.

No, that's not a renaming operation. a still exists; you haven't told the
compiler "stop accepting a as the name for this memory location, and
accept b instead". It's an aliasing operation: name b and name a both
refer to the same memory location and hence the same value.

Even if C++ had an operation for "delete this name from the compiler's
symbol table" (equivalent to del in Python), that's still a two-step
process: create a new name that co-exists with the original name, then
delete the original name. The fact that the C++ designers didn't see fit
to give the language a way to change a name demonstrates that it's not
the mutability of *names* which matter, but of *values* assigned to the
names.

I can think of a language where it would probably be possible, but non-
standard, to write a rename instruction: Forth. It should be possible to
use the tick instruction to get the address of a variable, constant or
function, then directly manipulate the compiler's dictionary (not like
Python dictionaries!) to change the name. But even Forth doesn't provide
a rename instruction as standard.

Well, you're off on the wrong track as far as convincing me about
something is concerned. First, your belief about renaming not being
supported by any languages is largely incorrect, as shown above.
Secondly, I was not talking about renaming things -- that creative
interpretation is pretty meaningless...

But if you talk about mutating *names*, then what else could it mean than
that you want to change the *name*? I can only respond to what you write,
not what you were thinking.

You wrote "routine names are usually constants, absolutely not meant to
be modified". Forget all about routines. If you had written:

"int names are usually constants, absolutely not meant to be modified"

then I'm sure you and I would agree: such a claim confuses the name with
the value assigned to the name. It's literally true that given an int n,
it is not usual to modify the *name* n regardless of whether n is a
variable or a constant. But that's irrelevant to the question of whether
n is a variable or a constant. That depends on the *value* assigned to n,
not the name itself. Exactly the same applies to functions.

If you think I'm harping on a trivial point of terminology, I guess
you're half-right: I can *guess* what you mean to say, namely that
function objects themselves are meant to be unmodified, and in Python it
is unusual to rebind names once they have been bound to a function.
(Unusual but not vanishingly so.) But that's just an assumption, and you
know what they say about assumptions.

Judging by what you actually say, and not what I assume you mean, your
reason for believing functions are constants is incorrect and illogical.
In languages that treat functions as constants, functions aren't treated
as constant because the name of the function is unchangeable (since
variables have unchangeable names too). They do so because the value (the
function itself) is unchangeable.
 
M

Mensanator

* Mensanator:







That seems to have no relation to what you quoted / responded to.

Whose fault is that? Here's a hint: when people's replies don't
make any sense, it's because they don't understand your prattle.
That's not good for someone who fancies himself a teacher of
programming.

On the other hand, if there is some specific rôle played by the 3 above, where
some other value (like e.g. 5) might be used instead, then a self descriptive
name for that rôle might be good.

Such reasonable naming (not what you did above) then allows easier modification
of and makes it easier to understand the code.

That said, and a bit off-tangent to your comment's main thrust, the time spent
on coding that repeated-division-by-2 optimization would, I think, be better
spent googling "Collatz Conjecture"  --  avoiding writing /any/ code. ;-)

Ha! I know more about Collatz than you can ever find by Googling!
And how did I achieve that? By writing such code. Be a good boy and
maybe I'll show you how to do Ulam's Spiral with Turtle Graphics.
If by "it" you mean the silly naming, no it doesn't.

On the contrary, it wastes time, both for writing the code and reading it..

Generally, IMO, think about the clarity of your code. If naming something
increases clarity, then name the thing. If it doesn't increase clarity, don't.









Yeah, good point. Few languages have compile time evaluation of logically
constant expressions. C++0x will have that feature (called 'constexpr' IIRC) but
in Python, current C++ etc. it's just a good idea to precompute values, and name
them, rather than computing them again and again where they're needed.



No, it depends on what you mean by "constant".

Why don't you try using what PEP 8 means by "constant".
The problem with Python, as
Google noted, is that the language is so excessively dynamic: even names of
routines are variables, and there /are/ no named user defined constants except
logically, in the programmer's mind. And logically (that is, at the "in the
programmer's mind" level), if you define "constant" as a name whose value will
not change after initialization, then routine names are constants.

You're sitting too close to the fire. Why don't you back off and
quit overanalizing things.
However, if you define "constant" as only a global scope (that is, module scope)
name that denotes a boolean, numerical or string or Null value and that doesn't
change after initialization, then your statement about the scarcity of constants
appears to be true, but using a practically useless definition.

I think for such constants exported by modules it's a good idea to at least
provide uppercase names so as conform to very firmly established convention.
There might even be tools that rely on that convention. But for application code
the uppercase names are just distracting, and they don't help you...

They help when I look at something like

a = (i-ONE)*NIN**(k-ONE) + (NIN**(k-ONE) - ONE)//TWO + ONE
 
A

Alf P. Steinbach

* Mensanator:
Ha! I know more about Collatz than you can ever find by Googling!
And how did I achieve that? By writing such code. Be a good boy and
maybe I'll show you how to do Ulam's Spiral with Turtle Graphics.

It's probably good for you that you know so much about Collatz.

I fail to see the relevance to anything.


Cheers & hth.,

- Alf
 
M

Mensanator

* Mensanator:





It's probably good for you that you know so much about Collatz.

I fail to see the relevance to anything.

No kidding. Here let me explain it:

You said my time would be better spent googling "Collatz Conjecture".

I said I know more than can be found by googling. Therefore,
it follows that my time could NOT be better spent googling.

Thus, your staement is shown to be false.

QED
 
L

Lie Ryan

As far as I know, no programming language provides a standard facility
for renaming entities, be they data or routines:

The C-preprocessor does to C/C++, in a limited fashion.
 
S

Steven D'Aprano

* Steven D'Aprano:

No. The code shown was like attacking Fermat's last theorem with a
little Python script checking out number triplets. It's already been
done (not to mention that that theorem's been proven, although that's,
AFAIK, not the case for Collatz').

You're assuming that Mensanator's motive for writing code is to challenge
the Collatz Conjecture, rather than to just have fun doing maths and
programming, or to build up his skills for a serious effort at extending
the domain of values for which it is known to be true. Or just because he
needs a function that calculates the hailstone numbers.


Now, it's a different story if you're using the gmpy module. You
DON'T want to use literals in loops involving gmpy, because they
would have to be coerced to .mpz's on every pass through the loop. [...]
Yeah, good point. Few languages have compile time evaluation of
logically constant expressions.

Surely that's an implementation issue rather than a language issue.

No, it isn't.

An optimizer can only do so much, as you yourself note below!

You make no sense here. What I note below is the existence of an
implementation-specific optimizer. And your conclusion? That such
optimization is language specific! How does that make sense?

Obviously it wouldn't be sensible for CPython to optimize numeric
literals as mpz objects, because that would be a lot of overhead for very
little gain. But it might make sense for somebody to create a "Numeric
Python" implementation which used mpz as the standard integer type.

A slightly more tricky case is optimizing away more complex runtime
expressions. len("abc") is not necessarily the constant 3, because the
function len may have been replaced by something else, thus requiring it
to be calculated at runtime rather than compile-time. But an
implementation might relax that condition and treat built-ins as
constants. Guido probably will never allow such a thing in CPython, but
other implementations are free to do things differently. Even if people
argue that such a behavioural change is "no longer Python", there's
nothing preventing an optimizing implementation from replacing
"abc".__len__() with the constant 3 except the complexity of
implementation and the diminishing returns of doing so.

With language support it's a very different matter because guarantees
propagate so that sophisticated analysis is no longer necessary: the
compiler /knows/, because it's explicitly being told.

Of course if a language makes a certain guarantee (for example, that
calls to math.exp(0) will be replaced at compile-time with 1.0) then the
compiler can make that substitution. But such optimizations tend not to
be specified by the language (in fact languages like C often tend to
leave large amounts of behaviour as implementation-defined), and even
when languages do make certain guarantees, implementations are free to
implement any behaviour not implicitly or explicitly forbidden.


No-ones been talking about renaming names. I think that's purely
rhetorical on your part but it may be that you really believe so. In the
latter case, just try to interpret statements so that they're meaningful
instead of meaningless. :)

(1) It's not meaningless to talk about renaming names.

(2) I will not try to guess what you mean on the basis of what I consider
sensible, rather than respond to what you actually say.

(3) I've already gone over the name/value thing to death in another post,
so I'll do everyone a favour and not continue it here.



[...]
I'm sorry, that's incorrect.

Quote from §4.1 "Naming and binding" of the Python 3.1.1 language spec:

"If a name is bound in a block, it is a local variable of that block,
unless declared as nonlocal. If a name is bound at the module level, it
is a global variable. (The variables of the module code block are local
and global.) If a variable is used in a code block but not defined
there, it is a free variable."

Since the above quote doesn't mention "constant", how on earth can you
use it as evidence for what "constant" refers to?

In any case, the words "constant" and "variable" have multiple meanings.
They can be either nouns or adjectives. Constants (nouns) are called
constants because of the value is constant (adjective) -- and similar for
variables.
 
A

Alf P. Steinbach

* Steven D'Aprano:
No, it's like saying that horses are not apes, because the definition of
apes excludes horses.

On the contrary. Apes are not superclass of horses.

But in any case, rather than continue my argument (which was absolutely
brilliant and flawless, I might add... *wink*), I'm going to skip ahead
to the place where the penny drops and I understand what you were trying,
but failed, to say.



*penny drops*

Ahaha!!! Now I get it! You want to make the *parameter* of the function a
constant, rather than the *argument* passed to the function. In other
words, you want to prohibit something like this:

def f(x):
y = x + 1 # do something with the value passed by the caller
x = 0 # changing the binding will fail INSIDE the function

while still allowing the caller to call the function with variables.

I would want the possibility of having that enforced, of course, because the
more you know about what can't take place in the code, i.e. the more easy-to-see
constraints there are, the easier it is to understand and reason about the code,
not to mention modifying it without breaking original assumptions.

But I wasn't talking about such a language feature (which I believe could also
greatly improve Python's efficiency by removing the need for many lookups).

Rather, I was remarking that in the absence of such a language feature, to be
consistent those who insist on using naming conventions to indicate design level
constraints should use those naming conventions also for this case and others.
For there's little point in making some occurrences of a constraint visually
explicit and others not. That just means that one cannot rely on the convention
to tell where the constraint is (meant to be) in effect or not.

Right -- now what you say makes sense, and is a perfectly reasonable
thing to do in languages that support constants.

You weren't clear about what you wanted, and the only thing I could think
of which matched your description was something completely bizarre: given
some function f with one parameter, you wanted to declare that parameter
as a constant with some value (say 42), so that calling the function with
an argument of any other value would be an error, e.g.:

x = 42
f(x) # succeeds
x = 43
f(x) # fails

E.g. implemented something like this:

def f(x):
if x != 42:
raise ConstantError('x is not the constant 42')
...

except that the test is done automatically by the compiler.



First of all, I think you're underestimating the usefulness and frequency
of rebinding names to functions. I'll accept that it's uncommon, but it's
not *that* uncommon to justify "absolutely not meant to be assigned to".

Yeah, hence the weasel term "in general".

In fact, it's so common that we have special syntax for one special case
of it. Instead of:

def f():
...

f = g(f)

we can write:

@g
def f():
...

While decorator syntax can only be used at function-definition time, the
concept of function decoration is far more general. You can decorate any
function, at any time, and doing so is very, very useful for (e.g.)
debugging, logging, monkey-patching, introspection, error-checking, and
others. And decoration itself is only one special case of function
rebinding.

But usually this wrapping occurs before the function is first used by other
code, i.e. it's usually part of initialization -- isn't it?

For debugging purposes you might want to replace a function on-the-fly, in the
middle of the program execution.

But debugging does all sorts of things not commonly accepted for normal execution.

As for your second point, my earlier comment is mostly aimed at what I
see as your misleading habit of referring to *names* as being constants,
rather than the value assigned to the name. Such terminology is
misleading. So if you want to call that rhetoric, I won't argue.

Not sure what that second point was (lost in response stack and snippage
somewhere), but as I remarked else-thread, in response to you, I'm sorry, but
your view about names is incorrect.

Quote from §4.1 "Naming and binding" of the Python 3.1.1 language spec:

"If a name is bound in a block, it is a local variable of that block, unless
declared as nonlocal. If a name is bound at the module level, it is a global
variable. (The variables of the module code block are local and global.) If a
variable is used in a code block but not defined there, it is a free variable."

It's not just terminology that a Python name "is" a variable, and vice versa.

Constness of the referred to value is an appropriate concept when that value is
conceptually mutable, like

v = [1, 2, 3]

This particular case is supported for the referent,

v = (1, 2, 3) # Voila, constant referent

which expresses and enforces the constness of the referent.

But to express the constness of the variable, which is what matters most of all
(for without that top level constness all other constness, such as of direct
referent, can be easily and inadvertently circumvented) all you can do is to
e.g. name it V instead of v, or put its creation in a region of text clearly
signalling "these are constants", or some such -- conventions.

Since a variable is a name, and in Python a name is a variable, "constness of a
variable" means "constness of a name". In Python. And as discussed above it's
about any name binding whatsoever, while the PEP 8 naming convention is limited
to communicating the intended constraint in just a few special cases, and hence
is unreliable by design (in addition to introducing lots of visual gruff).

No, that's not a renaming operation. a still exists; you haven't told the
compiler "stop accepting a as the name for this memory location, and
accept b instead". It's an aliasing operation: name b and name a both
refer to the same memory location and hence the same value.

Well, you can always do

int& b = a;
#define a __NO_SUCH_NAME__

He he. :)

Without the preprocessor you can do things like (or more elaborate)

int a = 666;
{
int& b = a; struct a;
// Only name b useful at this point. Most uses of a will be flagged.
}

And with the preprocessor that kind of renaming can be automated.

But seriously I thought you were referring to aliasing, because changing a name
mid-code generally makes no sense -- other than perhaps in an obfuscation
contest (where of course techniques like the #define shown above /are/ employed,
although typically with single letter names; it's a yearly contest for C...).


[snip]
But if you talk about mutating *names*, then what else could it mean than
that you want to change the *name*? I can only respond to what you write,
not what you were thinking.

In Python names are variables, per the language spec and in practice.

The language spec's saying:

"If a name is bound in a block, it is a local variable of that block, unless
declared as nonlocal. If a name is bound at the module level, it is a global
variable. (The variables of the module code block are local and global.) If a
variable is used in a code block but not defined there, it is a free variable."

Plus that of course, given this, the spec generally uses "variable" and "name"
as interchangeable synonyms, with just a little context-dependent favoring of
one or the other -- if you want example quotes I can provide a host of them.

And most variables, at least in code I'm familiar with, are constants, in the
sense that they're not intended to be re-bound after initialization.

And that's what I wrote, "most names are constants", and it was actually meant
to be precise: I was not talking about referred to objects, only about name
re-binding, that is, about changing what you get by id(N) for a name N.



Cheers & hth. (all cleared up now?),

- Alf
 
A

Alf P. Steinbach

* Steven D'Aprano:
You're assuming that Mensanator's motive for writing code is to challenge
the Collatz Conjecture, rather than to just have fun doing maths and
programming, or to build up his skills for a serious effort at extending
the domain of values for which it is known to be true. Or just because he
needs a function that calculates the hailstone numbers.

I would rather not speculate about motives.

I can say things about the code that was presented, and I did, but as for the
motives that went into creating it or presenting it in this thread, well I'm not
telepathic.

Now, it's a different story if you're using the gmpy module. You
DON'T want to use literals in loops involving gmpy, because they
would have to be coerced to .mpz's on every pass through the loop.
[...]
Yeah, good point. Few languages have compile time evaluation of
logically constant expressions.
Surely that's an implementation issue rather than a language issue.
No, it isn't.

An optimizer can only do so much, as you yourself note below!

You make no sense here. What I note below is the existence of an
implementation-specific optimizer. And your conclusion? That such
optimization is language specific! How does that make sense?

No, I meant exactly what I wrote, that an optimizer only can do so much, which
you also noted yourself; that language support facilitates this optimization;
and that few languages have that support (but e.g. C++0x will have it).


[snip]
Of course if a language makes a certain guarantee (for example, that
calls to math.exp(0) will be replaced at compile-time with 1.0) then the
compiler can make that substitution.

Yes, that's what C++0x 'constexpr' is about.

But I've lost the context here, not sure what this is all about.


[snip]
(1) It's not meaningless to talk about renaming names.

Well, as good as meaningless, IMHO. In fact, earlier, when you wrote under the
assumption that I was talking about such renaming, you seemed to maintain that
it was pretty meaningless. I conclude from that that also this is relative.

(2) I will not try to guess what you mean on the basis of what I consider
sensible, rather than respond to what you actually say.

I rather hoped you would try to employ the "sensible" heuristic.

The problem so far has, apparently, been that you refuse to accept the language
specification's definition, in §4.1, of variable as name and vice versa.

An "is-it-sensible" heuristic could have cleared that up, I think.

(3) I've already gone over the name/value thing to death in another post,
so I'll do everyone a favour and not continue it here.

But when that is the problem, a basic misunderstanding of what a word means,
then it's worth uh, harping on the issue, I think:

[...]
I'm sorry, that's incorrect.

Quote from §4.1 "Naming and binding" of the Python 3.1.1 language spec:

"If a name is bound in a block, it is a local variable of that block,
unless declared as nonlocal. If a name is bound at the module level, it
is a global variable. (The variables of the module code block are local
and global.) If a variable is used in a code block but not defined
there, it is a free variable."

Since the above quote doesn't mention "constant", how on earth can you
use it as evidence for what "constant" refers to?

I don't. I use it as evidence for what "name" refers to. A constant "name" is
then, logically, a constant such thing, to wit, a constant variable. You may
find that terminology paradoxical. For C++, which has the means of enforcing it,
it's a FAQ (many novices have some trouble accepting constant /variables/...).
If it helps let's adopt Java's terminology and talk about "final" variables. Or,
as Bjarne had it in original C++, "readonly" variables.

In any case, the words "constant" and "variable" have multiple meanings.
They can be either nouns or adjectives. Constants (nouns) are called
constants because of the value is constant (adjective) -- and similar for
variables.

Yeah, but you're not seriously suggesting that any global variable initialized
to a constant (immutable value) should be uppercased. So "constant" in this
regard does not refer to the refererred to value. It refers to the id(v).


Cheers & hth.,

- Alf
 
J

John Posner

Chapter 2 is about Basic Concepts (of programming). It's the usual:
variables, ...

1. Overall suggestion

You have a tendency to include non-pertinent asides [1]. But then,
rambling a bit endows a manuscript with the author's distinctive voice.
Fortunately, we live in a hypertext-enabled world, where you can have your
cake and eat it, too. I suggest that you go over your manuscript with a
ruthless eye, and turn your rambles into hypertext-accessible "sidebars"..
See how much you can reduce the length of Chapter 2, which current runs 98
pages!

2. Comments on Section 2.6.7, "References & automatic garbage collection"

There's a spell-check evader on page 2:49: "trough" s.b. "through". And
your spell-checker should have caught "adressable" on page 2:48.

I find your sequence-of-attribute-lookups approach to the topic of
"variable assignment" interesting. The directed-graph illustration on page
2:49 even hints at the fact that in the world of Python, names ("turtle",
"forward", etc.) and objects (various kinds of yellow boxes) are very
different things.

(I suggest getting rid of the callout "Very small fixed size variable".
That way, only objects, not names, have the italicized callouts.)

But using the term "references" and the directed-graph metaphor has its
drawbacks. Avoiding the term "reference" will make it easier to navigate
the turbulent waters of call-by-reference vs. call-by-value vs.
call-by-name. (You don't even stick a toe in those waters in Section
2.7.5.) Combining memory addresses with the directed graph metaphor
invites the reader to think at way too low a level, IMHO.

Another metaphor just occurred to me: a scavenger hunt. It even fits in
with your potentially-infinite-attribute-access approach to the topic. A
sequence of attribute accesses:

turtle.forward.__doc__

.... is like a sequence of scavenger-hunt instructions:

1. Get your next clue at the big oak tree
2. Get your next clue at the back door of the Valley Bank
3. Get your next clue under Dad's Oldsmobile

It's clear that the scavenger hunt's clues (short characters strings --
like Python names) are different from the physical objects that you access
as the hunt progresses (tree, bank building, automobile -- like Python
objects). I haven't lived with this metaphor long enough to form an
opinion as to where it might reside on the brain-dead <---> brilliant
scale.

As I've said in this forum (and the edu-sig forum) before, I think the
best metaphor for understanding Python variable assignment is John Zelle's
yellow-sticky-note metaphor. [2]

I hope these comments help.

-John

--------------
[1] Examples:

Section 2, page 2:1

It's almost impossible, but, as Robert A. Heinlein remarked,
"A Paradox May Be Paradoctored".

Section 2, page 2:3

(I believe the original text comes from the "Jargon file")
about how arbitrary, undesirable and downright dangerous DWIM
guessing can be: ...

Section 2.5.1, page 2:14

a natural extension is to make that into a square spiral with
far more windings; I recall it as a common theme in 1970’s
pop-art and it can be quite impressive!

Section 2.6.7, page 2:46

(some flat-Earthers once thought that the flat Earth rested
on four turtles, which in turn stood on four larger
turtles, and so on all the way down)

[2] "Python Programming: An Introduction to Computer Science" by John
Zelle (Franklin, Biddle & Associates, 2004) See Section 2.5.1, "Simple
Assignment"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top