code review

C

Chris Angelico

Kernighan and Ritchie admit they made a design mistake with their
operator precedences:

http://en.wikipedia.org/wiki/C_(programming_language)#Criticism

The examples given there have nothing to do with the chaining of
comparisons and how it's to be interpreted. Yes, "x & 1 == 0" is
evaluated oddly. Doesn't affect "x == 1 == y".

Python's interpretation is more algebraic than C's. That doesn't mean
that C is wrong and Python is right, nor does it mean the converse.

ChrisA
 
C

Chris Angelico

Nonsense. Of course parens change the evaluation of the expression.
That's what parens are for!

The whole point of my example was that it wouldn't.

ChrisA
 
E

Evan Driscoll

C, SQL, REXX, and many other languages.

Some others: Lua, Javascript, Ruby, O'Caml.

In fact, the only language I can find that uses infix notation (i.e. no
Lisp) where it's *not* true that "a < b < c" is equivalent to "(a < b) <
c" is Haskell -- and that's because < is not associative and "a < b < c"
is a syntax error. (FWIW this is my favorite approach.) You may also
want to put Java in there as well, as < is effectively not commutative
in that language. (I didn't try C#.)

I've been programming in Python for a few years and this is the first
time I've seen this. If I had seen that in a program, I'd have assumed
it was a bug.

Evan



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJP79XuAAoJEAOzoR8eZTzgiXoH/jK9d3pZ5YSAlFB/KUChLPzX
4FkzVh5DSTTT69KcgaDpHnuigzL+0ltZmLK8BItEH3pd3LfcsegYbaJ7I6UExtUS
BQhnGvQJS26Gc/8xDvaGdCSsYAFCxc1QvB/LR4LnwNmBAjHlKNLf0k79LP72FBc4
8dRopp11FL3TG2BXFnUO+6PCvEgICgWXzPQJc8odittJXjQX9Ticr2ZkhLYCnFdR
e4csHh6x+g2m3JMu0jkzMXvDIZQ9F+o4CKJeBGt8Ag6YEsbV/w1t2JkskCn6uoVA
AC6Em1HMYGV9ysUKJH6AjuRLcBDKXhsgYWxJIQTkSycdgyn+jYmVm4yKGLCD3Kw=
=uFq6
-----END PGP SIGNATURE-----
 
E

Evan Driscoll

You may also
want to put Java in there as well, as < is effectively not commutative
in that language. (I didn't try C#.)

I guess you could actually put Lua and Ruby into the roughly same
category as Java too.

But things get a little nastier in ==, as 'false == false == false'
evaluates as '(false == false) == false' to 'false' in Java and Lua.
(Ruby produces a syntax error for this, roughly the Haskell approach.)

But we have Javascript:
1 < 1 < 2
=> true
false == false == false
=> false

O'Caml:
# false == false == false;;
- : bool = false
# 1 < 2 < true;;
- : bool = false

Java:
System.out.println(false == false == false);
(outputs) false

Lua:
print(false == false == false)
false

C and C++:
std::cout << (1 < 1 < 3) << "\n";
(outputs) 1
std::cout << (false == false == false) << "\n";
(outputs) 0

Evan


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJP79jAAAoJEAOzoR8eZTzgeZ0H/3Cp277/wQSdbM8/SOf0PMeN
jkXMSqCXpqATFLipgsxC0oIQg8G3UEPknG+ZDfqpj1t8eS6BJVtSqxYs9pAlHYqr
y6dcbYieDW6Kq7t+JprUdWxXpvdw0Ddqr3aEJS+vHlDZv2tb9xxVU1WPt4JeZ5IP
N+vKg9qXHRO2b9t8QfEZZ+5LO6KrdO9jNb2VvmemZ4wJa9W8tp0zVYagct11YuaB
WTKTvBzo/o/agL/gHE5tXvRBukyjmI0Vs2XF9iLyt+qEVlsJ7vk6d+TZBQ4bWp5U
g49Lh1cCaMqbR/77vt21MDjlTe6a8Hmdv5xT/47kQP55VVrPtFgY4XP8ItaJHWw=
=tOM7
-----END PGP SIGNATURE-----
 
S

Steven D'Aprano

The whole point of my example was that it wouldn't.

Yes, you can find specially crafted examples where adding parentheses in
certain places, but not others, doesn't change the overall evaluation of
the expression. So what? IN GENERAL, adding parentheses changes the
evaluation of the expression -- that is what they are for.

Therefore, IN GENERAL you should expect that adding parentheses will
change the result, unless you carefully place them where you know that
they will have no effect.

Even in C, I can't just do this:

2+3*4
=> (2+3)*4

with the expectation that you can stick parentheses around the left-most
term without changing the value. The fact that you can do for some
expressions is irrelevant.

In general, if you don't know the semantics of an expression (including
the operator precedence), you cannot just assume that adding parens is
harmless.
 
C

Chris Angelico

Yes, you can find specially crafted examples where adding parentheses in
certain places, but not others, doesn't change the overall evaluation of
the expression.

My point was that adding parentheses around the tightest-binding
operator is a common, clear, and usually insignificant, way of
demonstrating operator precedence. So FOR THAT USE they must not
change evaluation of the expression. Obviously if you put them
anywhere else, they change evaluation. Nice job knocking down a straw
man.

ChrisA
 
S

Steven D'Aprano

Not so. It simply means that booleans are nothing special. In REXX,
there are no data types at all, and "1" and "0" are your booleans. In C,
boolean and comparison operations return integers, either 1 or 0.

This has nothing to do with the presence of a Boolean type or not. It is
about syntax, not types.

Python didn't have bools until relatively recently but it still had
chained comparisons. In Python2.1 and older, boolean and comparison
operations return integers, either 1 or 0, precisely the same as C.

You can even use chained comparisons for types that don't interpret the
operators as comparisons:

py> class Funny:
.... def __init__(self, x):
.... self.x = x
.... def __lt__(self, other):
.... return Funny(self.x + 3*other.x)
.... def __str__(self):
.... return str(self.x)
....
py> a = Funny(2)
py> b = Funny(3)
py> c = Funny(4)
py>
py> print a < b < c
15
py> print (a < b) and (b < c)
15

Although the interpretation of such may not be useful.


Same
was true of Python early on, if I understand correctly. It's not
shameful.

The first public release of Python, 0.9, included chained comparisons.
This was even before the language had == for equality tests!

steve@runes:~$ ./python0.9.10


So no, Python has always included chained comparisons, and yes, it is
shameful that a language would force you to unlearn standard notation in
favour of a foolish consistency with other operators. Comparisons aren't
special because they return bools. They are special because of the way
they are used.

C treats comparison operators as if they were arithmetic operators, and
so the behaviour of a chained comparison is the same as the behaviour as
a sequence of arithmetic operators: a foolish consistency. Python treats
comparison operators as comparison operators, and gives them behaviour
appropriate to comparisons.

The fact that so many languages do the wrong thing here, and so few
emulate standard mathematical notation, is symptomatic of the lousy state
of language design.

Pascal might be verbose, but at least it makes it harder to write code
that silently does the wrong thing -- it prevents you from writing
chained comparisons which do the wrong thing, and forces you to be
explicit. C has the worst of both worlds:

- it allows the standard concise mathematical notation a < x < b
- but it quietly changes the semantics to something that the user
hardly ever wants

thus giving the maximum number of bugs with the minimum amount of
convenience.

It makes perfect sense though;

Not in English-speaking countries with a culture of writing chained
comparisons in mathematics and allowing them in natural language:

"Rock is beaten by Paper, is beaten by Scissors".
 
T

Thomas 'PointedEars' Lahn

Evan said:
Some others: Lua, Javascript, Ruby, O'Caml.

JFTR: Contrary to popular belief there is no programming language named
"Javascript". In your second point, you are talking about ECMAScript
implementations; one of which is _JavaScript_, Netscape's and later
Mozilla.org's ECMAScript implementation. Other implementations include that
of Microsoft, JScript.

<http://PointedEars.de/es-matrix>
 
C

Chris Angelico

Not in English-speaking countries with a culture of writing chained
comparisons in mathematics and allowing them in natural language:

"Rock is beaten by Paper, is beaten by Scissors".

I would write that as:

Rock is beaten by Paper, and beats Scissors.

That's natural language. Unless you put a "which" in the middle, the
grammar of English demands that the subject be common, not a promoted
object.

ChrisA
 
T

Thomas 'PointedEars' Lahn

Evan said:
I guess you could actually put Lua and Ruby into the roughly same
category as Java too.

But things get a little nastier in ==, as 'false == false == false'
evaluates as '(false == false) == false' to 'false' in Java and Lua.
(Ruby produces a syntax error for this, roughly the Haskell approach.)

But we have Javascript:

s/Javascript/ECMAScript (implementations)/g
1 < 1 < 2
=> true
false == false == false
=> false

Correct, because

0. 1 < 1 < 2
1. (1 < 1) < 2
2. false < 2
3. 0 < 2
4. true

and

0. false == false == false
1. (false == false) == false
2. true == false
3. false

See also the ECMAScript Language Specification, 5.1 Edition, section 11.9:

<http://ecma-international.org/ecma-262/5.1/#sec-11.9>
 
T

Thomas Jollans

Well that's your problem. Why are you reading it as a ternary operator?
It isn't one. It is a pair of chained binary operator.

How can you tell that it is a pair of binary operators, rather than a
single ternary operator?

1) There are TWO operators, hence a pair, not a single ternary operator;

2) each operator takes TWO arguments, not three.

This is simply wrong. The comparisons are not acting as binary operators.
You can't just arbitrarily stick parentheses around parts of expressions
and expect the result to remain unchanged. Order of evaluation matters:

2**3**4 != (2**3)**4

Comparisons are no different. You can't just toss in some arbitrary
brackets and expect the result to be the same. In the second case, the
parentheses explicitly turn the comparison into the equivalent of this:

As Chris points out, 2**3**4 == 2**(3**4)

For any chain of actual binary operators, it is possible to place
brackets around binary sub-expressions such that the meaning of the
total expression stays the same. This is true of ALL infix-notated
strictly binary operators. This is not true of Python comparison operators.

If you're inclined to look at operators in terms of how many arguments
they take, then the inescapable conclusion is that a chain of N
comparison operators forms one single (N+1)-term operator because it
cannot be grouped into binary expressions without rephrasing the
expression using "and".
 
T

Thomas Jollans

So, languages without strong typing then. In that case, I revise my
statement: I know of no programming language with strong typing that
would give a newcomer to Python the above expectation.

Since Python does have strong typing, norms about operations from
weakly-typed languages should not be expected to apply.

(Incidentally, PostgreSQL was the SQL implementation I went to, and::

postgres=# SELECT (1 < 2) < 3;
ERROR: operator does not exist: boolean < integer
LINE 1: SELECT (1 < 2) < 3;
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.

So not all SQL implementations make the mistake of weak typing.)

I don't have PostgeSQL handy just now - what is the result of
(1 < 2 < 3) ? I bet it's the same error, which means the two are still
equivalent.

Look through the part of this thread about languages, and you will see
how unique Python is in this regard. You argument about strong typing
doesn't really hold: in Python, as in some other languages, bool is an
integer type, so it can actually be compared to numbers, so even if
Python used the same rules as Java, chaining comparison operators would
still be valid syntax (if a little silly in most cases)
 
D

Devin Jeanpierre

So, languages without strong typing then. In that case, I revise my
statement: I know of no programming language with strong typing that
would give a newcomer to Python the above expectation.

OCaml is a language with absurdly strong typing, where a < b < c is
equivalent to (a < b) < c.

Obviously, this only works if c is a boolean, and if a and b are the
same type. Otherwise it is a type error.

Also, you claimed earlier that the notion of associative "<" is not
founded in mathematical notation. It really depends on whose
mathematical notation you use -- there's more than one, you know. For
example, it's reasonable to expect < to be left- or right-associative
in a system like Rick Hehner's Unified Algebra:
http://www.cs.toronto.edu/~hehner/UA.pdf (although, he himself doesn't
specify it as being one or the other, so by default one would assume
'a < b < c' to be nonsensical.)

-- Devin
 
A

Alister

Some others: Lua, Javascript, Ruby, O'Caml.

In fact, the only language I can find that uses infix notation (i.e. no
Lisp) where it's *not* true that "a < b < c" is equivalent to "(a < b) <
c" is Haskell -- and that's because < is not associative and "a < b < c"
is a syntax error. (FWIW this is my favorite approach.) You may also
want to put Java in there as well, as < is effectively not commutative
in that language. (I didn't try C#.)

I've been programming in Python for a few years and this is the first
time I've seen this. If I had seen that in a program, I'd have assumed
it was a bug.

Evan

You would?
I have only been using python for 6 - 12 months but in my past I
programmed microcontrollers in assembly.

as soon as i saw it i understood it & thought great, like a light bulb
going on.

I suppose I have the advantage that it is only the taint of BASIC (in the
home computer era) that I have had to overcome and my programming style
has not been unduly influenced by c & others.

it is easy to write C, or Pascal or even BASIC in python but why bother,
why not just use C, Pascal or BASIC in that case?
 
T

Terry Reedy

So no, Python has always included chained comparisons, and yes, it is
shameful that a language would force you to unlearn standard notation in
favour of a foolish consistency with other operators. Comparisons aren't
special because they return bools. They are special because of the way
they are used.

C treats comparison operators as if they were arithmetic operators, and
so the behaviour of a chained comparison is the same as the behaviour as
a sequence of arithmetic operators: a foolish consistency. Python treats
comparison operators as comparison operators, and gives them behaviour
appropriate to comparisons.

I considered this a great feature of Python when I first learned it.
Reading about how rare it is among programming languages to treat
comparisons in the standard way in mathematics reinforces that.
 
E

Evan Driscoll

You would?
I have only been using python for 6 - 12 months but in my past I
programmed microcontrollers in assembly.

as soon as i saw it i understood it & thought great, like a light bulb
going on.

It's not a matter of "I wouldn't have understood what the author was
trying to do" (with a small caveat), it's a matter of "I'd have assumed
that the author didn't understand the language semantics."

I'm pretty sure it goes contrary to *every other programming language
I've ever used* (and a couple that I haven't).

I understand why Python does it, and it certainly is nice in that it
matches typical mathematical notation, but the surprise quotient is
*very* high in the PL world IMO.

Evan
 
D

Dennis Lee Bieber

So, languages without strong typing then. In that case, I revise my
statement: I know of no programming language with strong typing that
would give a newcomer to Python the above expectation.
I'd think a true newcomer (to programming) would have NO
expectations... And if they'd had any complex math classes may actually
consider
if 1 < x < 10:
to be the norm (though it should be less-than-or-equal to map to the
normally inclusive endpoints in "pick a number between 1 and 10")

-=-=-=-
Assignment: Conditionals and Looping

The program shall play "Guess the Number" with the user.
The program shall generate an random integer between 1 and 10
(including 1 and 10 in the range).
The program shall loop asking the user for a guess.
The program shall reject non-integer input.
The program shall reject integers outside the range of 1 to 10.
The program shall maintain a score (count) of incorrect guesses.
The program shall display the score and exit when the user's guess
matches the randomly generated integer.
-=-=-=-


In contrast to this total neophyte, someone transferring from
another language should have enough experience to at least browse the
language reference and tutorial -- especially after encountering the
simple fact that Python requires indentation to denote blocks! Something
not seen in any other language and which should trigger the thought
"what else is different?"

At least the Python reference is only around 100 pages (last time I
printed it) in contrast to the 800 page Ada 2005 (okay, the appendices
should be counted as equivalent to the Python standard library
reference; that still leaves over three times as many pages for Ada
reference, and the print is much finer)
 
H

HoneyMonster

I don't have PostgeSQL handy just now - what is the result of (1 < 2 <
3) ? I bet it's the same error, which means the two are still
equivalent.

$ psql misc

psql (9.1.4)

Type "help" for help.

misc=# select (1 < 2);

?column?

----------

t

(1 row)

misc=# select (1 < 2 < 3);

ERROR: syntax error at or near "<"

LINE 1: select (1 < 2 < 3);
^
misc=#
 
S

Steven D'Aprano

Also, you claimed earlier that the notion of associative "<" is not
founded in mathematical notation. It really depends on whose
mathematical notation you use -- there's more than one, you know. For
example, it's reasonable to expect < to be left- or right-associative in
a system like Rick Hehner's Unified Algebra:
http://www.cs.toronto.edu/~hehner/UA.pdf (although, he himself doesn't
specify it as being one or the other, so by default one would assume 'a
< b < c' to be nonsensical.)

Sheesh guys. Don't go hunting through the most obscure corners of
mathematics for examples of computer scientists who have invented their
own maths notation. (Which, by your own admission, is under-specified and
therefore incomplete.) Who uses Hehner's "Unified Algebra" notation?
Apart from Hehner, if he even uses it himself.

Pick up any standard maths book and you will see chained comparisons used
with exactly the meaning that Python gives them.

For example, Wolfram MathWorld uses it:

http://mathworld.wolfram.com/Inequality.html


Chained comparisons in the Python sense may be rare in computer
languages, but it is the standard in mathematics and hardly needs to be
explained to anyone over the age of twelve. That is a terrible indictment
on the state of programming language design.
 
S

Steven D'Aprano

This is simply wrong. The comparisons are not acting as binary
operators.

Of course they are. Take this chained comparison:

a < b == c

There are exactly TWO operators. Each one takes TWO arguments.

The < operator takes a and b as arguments. That's TWO, not three.

The == operator takes b and c arguments. Again, that's TWO, not three.

If you want to claim that this is a ternary operator, you need to explain:

1) What is the operator in this expression? Is it < or == or something
else?

2) What double-underscore special method does it call? Where is this
mysterious, secret, undocumented method implemented?

3) Why do the Python docs lie that a < b == c is exactly equivalent to
the short-circuit expression (a < b) and (b == c) with b evaluated once?

4) And how do you explain that the compiled byte code actually calls the
regular two-argument binary operators instead of your imaginary three-
argument ternary operator?

py> from dis import dis
py> dis(compile("a < b == c", "", "single"))
1 0 LOAD_NAME 0 (a)
3 LOAD_NAME 1 (b)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 0 (<)
11 JUMP_IF_FALSE_OR_POP 23
14 LOAD_NAME 2 (c)
17 COMPARE_OP 2 (==)
20 JUMP_FORWARD 2 (to 25)26 LOAD_CONST 0 (None)
29 RETURN_VALUE
 

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

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,165
Latest member
JavierBrak
Top