code review

S

Steven D'Aprano

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.

Apart from Python, Mathematica, Perl 6, CoffeeScript, Cobra and Clay give
chained comparisons the standard meaning. It is, or was, a feature
request for Boo, but I can't tell whether it has been implemented or not.


C-like semantics are next to useless, except perhaps for obfuscation:

http://stackoverflow.com/questions/4089284/why-does-0-5-3-return-true/

And surprising:

http://answers.yahoo.com/question/index?qid=20090923172909AA4O9Hx

C-like semantics are a clear case of purity of implementation overruling
functional usefulness.
 
S

Steven D'Aprano

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.

We *really did have* somebody arguing that chained comparisons are Bad
because you can't stick parentheses around bits of it without changing
the semantics. That was an actual argument, not a straw-man.

It's a stupid argument, but don't blame me for pointing out it's
stupidity. The author *assumed* that a chain of < must be left-
associative in the same way that a chain of + operators is left-
associative, but it isn't. That's an invalid and unsafe assumption --
even in C-like languages, there are operators which aren't left-
associative, e.g. exponentiation ** which is usually right-associative.
(For the record, C itself doesn't have an exponentiation operator.)

When you make unsafe assumptions about an operator, and get bitten by it,
the *correct* conclusion should be that the assumption was wrong, not
that the language is wrong.

Technically, < in Python is left-associative: a < b < c first evaluates
a, not b or c. But it is left-associative under the rules of comparison
operator chaining, not arithmetic operator chaining.
 
D

Devin Jeanpierre

On Sun, 01 Jul 2012 05:18:09 -0400, Devin Jeanpierre wrote:
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.

I didn't hunt, I was taught it in university.

-- Devin

(Of course, it shouldn't be hard to guess who the professor was :)
 
D

Devin Jeanpierre

Technically, < in Python is left-associative: a < b < c first evaluates
a, not b or c. But it is left-associative under the rules of comparison
operator chaining, not arithmetic operator chaining.

Left-associativity is when a < b < c is equivalent to (a < b) < c.

You're talking about evaluation order, which can be different. For
example, hypothetically, (a < b) < c could evaluate c first, then b,
then a. However, Python always evaluates operands left-to-right.

A particular case where this comes into play is the ** operator, which
is right-associative but still has a left-to-right evaluation order.

-- Devin
 
C

Chris Angelico

We *really did have* somebody arguing that chained comparisons are Bad
because you can't stick parentheses around bits of it without changing
the semantics. That was an actual argument, not a straw-man.

Okay, I take back the "straw man" accusation, and apologize for it.
But you were quoting my text at the time, so I thought you were aiming
at my argument - which, not being that, was what led me to think you
were answering what you weren't answering.
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.

I'd say that proves that Python is a good language for expressing
mathematics in, then. That's all. Doesn't necessarily mean it's good
for any other task (doesn't mean it's bad either of course). Python
does not, meanwhile, have inbuilt operators for calculus, nor does it
have an equation solver. Do we absolutely need them? Empirically no.
Python can be an excellent language without making every bit of
mathematical notation executable. There are, I am sure, plenty of
cases where it would be nice to go:

x = y+2
x*3 = y*4+7
print("x = %d, y = %d",x,y)

You can argue that Python ought to have more-different operators for
comparison and assignment, but the fact of algebra is that it has
neither - the equals sign is more of a declaration of truth. Algebra
simply isn't imperative. It's fine (and fits the Eliza principle) to
evaluate expressions algebraically, but equations aren't assignment.

ChrisA
 
J

John O'Hagan

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
[...]

+1

I've only ever known Python (well, I've almost forgotten Bash), and when I
first needed a range test, I guessed at the above form and was pleasantly
surprised that it worked: it seemed too good to be true that Python was smart
enough to know I wanted the same "x" to be an operand in two comparisons at
once.

John

P.S. I know I'm not helping, but I'm starting to feel sorry for the guy who
wanted his code reviewed!

John
 
D

Dennis Lee Bieber

When you make unsafe assumptions about an operator, and get bitten by it,
the *correct* conclusion should be that the assumption was wrong, not
that the language is wrong.
Well, to be fair, the assumption is not about "an operator" but
about a sequence of operators. I don't think there was any question that
"<" was not a "less than comparison" operator.

OTOH: consider
vs

5 ^ 2 [1] 25
1 < 3 < 5
Error: unexpected '<' in "1 < 3 <"
TRUE < 1 [1] FALSE

vs

? 5 ^ 2
25
? 1 < 3 < 5
True
? 3 < 5 < 1
True
? True < 1
True

vs

PS E:\UserData\Wulfraed\My Documents> 5 ^ 2
Unexpected token '^' in expression or statement.
At line:1 char:3

Unexpected token '2' in expression or statement.
At line:1 char:5

PS E:\UserData\Wulfraed\My Documents> 1 -lt 3 -lt 5
False

PS E:\UserData\Wulfraed\My Documents> 1 -lt 5 -lt 3
False

PS E:\UserData\Wulfraed\My Documents> 1 -lt 5
True

PS E:\UserData\Wulfraed\My Documents> True -lt 1
The term 'True' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if
a path was included, verify that the path is correct and try again.
At line:1 char:5
+ True <<<< -lt 1
+ CategoryInfo : ObjectNotFound: (True:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

PS E:\UserData\Wulfraed\My Documents> 'True' -lt 1
False

PS E:\UserData\Wulfraed\My Documents> 1 -lt 'True'
Bad argument to operator '-lt': Could not compare "1" to "True". Error:
"Cannot convert value "True" to type "System.Int32". Error: "Input
string was not in a correct format."".
At line:1 char:6
+ 1 -lt <<<< 'True'
+ CategoryInfo : InvalidOperation: :)) [], RuntimeException
+ FullyQualifiedErrorId : BadOperatorArgument

PS E:\UserData\Wulfraed\My Documents> $True -lt 1
False

PS E:\UserData\Wulfraed\My Documents> $True -eq 1
True



Python, R, Visual Basic 6, PowerShell 2, respectively.

Obviously, someone coming over from VB or R who hasn't read the
Python reference is going to wonder why they are getting incorrect
results when doing exponentiation (Isn't there an interface from Python
to R? Now there is confusion in the making!)

{Hmmm... R accepts BOTH 5 ** 2 and 5 ^ 2 for exponentiation}
 
S

Steven D'Aprano

Left-associativity is when a < b < c is equivalent to (a < b) < c.

You're talking about evaluation order, which can be different. For
example, hypothetically, (a < b) < c could evaluate c first, then b,
then a. However, Python always evaluates operands left-to-right.

A particular case where this comes into play is the ** operator, which
is right-associative but still has a left-to-right evaluation order.

Yes, you are right, my mistake.
 
S

Steven D'Aprano

I'd say that proves that Python is a good language for expressing
mathematics in, then. That's all.

No. Python is a terrible language for expressing mathematics in. As you
point out, I can't do things like:

x = y+2
x*3 = y*4+7
print("x = %d, y = %d",x,y)

and sensibly get x = 1, y = -1.


But Python is an excellent language for expressing a series of
comparisons in, which has applications beyond pure maths or algebra. For
example:

"c" < first_word < second_word == third_word < "x"

I'm sure I don't have to explain what that means -- that standard chained
notation for comparisons is obvious and simple.

In Python, you write it the normal way, as above. But some other
languages force you into verbosity:

("c" < first_word) and (first_word < second_word) and (second_word ==
third_word) and (third_word < "x")

Worst of all are those languages which allow you to write the expression
as normal, but evaluate it in a way that you almost never want: the
maximum number of bugs with the minimum convenience.

This has nothing to do with algebra. It is about picking semantics for
chained comparisons which is sensible and useful and matches what people
expect from regular language.

If you write 2+2 = 2*2 = 4, nearly everyone will agree that, yes, that is
true. Interpreting it as 1 == 4 is neither sensible nor useful and it is
certainly not what people expect.
 
C

Chris Angelico

"c" < first_word < second_word == third_word < "x"

I'm sure I don't have to explain what that means -- that standard chained
notation for comparisons is obvious and simple.

In Python, you write it the normal way, as above. But some other
languages force you into verbosity:

("c" < first_word) and (first_word < second_word) and (second_word ==
third_word) and (third_word < "x")

Uhh, actually you DO have to explain that, because I interpreted it
quite differently:

(("c" < first_word) and (first_word < second_word)) == (third_word < "x")

And even if you can prove that my interpretation is wrong, it's still
plausible enough that I, as a programmer, would have to dive to the
manual (or test in interactive interpreter) to find out which way the
language evaluates this.

ChrisA
 
T

Thomas Jollans

Of course they are. Take this chained comparison:

Technically, yes - two-input operations are happening. Syntactically,
no. Read my post.
1) What is the operator in this expression? Is it < or == or something
else?

I think I've answered this - it's the combination.
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?

In this context, I don't care what actually happens. I'm talking about
how the code can be parsed (by the generic reader, not necessarily the
python interpreter).
 
T

Thomas Jollans

We *really did have* somebody arguing that chained comparisons are Bad
because you can't stick parentheses around bits of it without changing
the semantics. That was an actual argument, not a straw-man.

Ahem. It may have been sub-optimally phrased in a way that opened itself
up to attack, but I was arguing that Python comparisons operators are
anomalous because they're not associative.

(and, going back to the root of the argument, this makes them Bad
because "Special cases aren't special enough to break the rules.")
 
R

Rick Johnson

Good grief. Why would you expect that?

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

Yes but as Chris points out in the next message, you can inject the
following parenthesis without changing a thing!:

py> 1 + 3 * 4
13
py> 1 + (3 * 4)
13

Of course i understand the rules of operator precedence, however i
have never liked them AND i continue to believe that such
functionality breeds bugs and is in fact bad language design. I
believe all evaluations should be cumulative:

py> 1 + 3 * 4
should ALWAYS equal 16!

With parenthesis only used for grouping:
py> a + (b*c) + d

Which seems like the most consistent approach to me.
 
T

Terry Reedy

Obviously, someone coming over from VB or R

or any other single language x
who hasn't read the Python reference is going to

be surprised as something or other. So what. The manuals, including the
tutorial, are there for a reason. The main points of the language take
just a few hours to review.

Perhaps my advantage learning Python was that I had written code in over
10 other languages and dialects and so had few expectations and made few
assumptions.
 
R

Rick Johnson

Uhh, actually you DO have to explain that, because I interpreted it
quite differently:

(("c" < first_word) and (first_word < second_word)) == (third_word < "x")

Poor Chris. That's because you've been brainwashed into believing you
must spoon feed your interpreter to get your code working correctly.
Stop applying these naive assumptions to Python code. Python knows
when you reach the end of a statement, no need for redundant
semicolons! Python knows when its reached the end of block and needs
to drop back one level, no need for redundant road signs. Python
knows Chris; Python KNOWS!
 
C

Chris Angelico

Poor Chris. That's because you've been brainwashed into believing you
must spoon feed your interpreter to get your code working correctly.
Stop applying these naive assumptions to Python code. Python knows
when you reach the end of a statement, no need for redundant
semicolons! Python knows when its reached the end of block and needs
to drop back one level, no need for redundant road signs. Python
knows Chris; Python KNOWS!

Why "poor", Ralph?

I am poor in the essence of ignorance's bliss, rich only in the
never-ending thirst for knowledge and more languages. In me there meet
a combination of antithetical elements which are at eternal war with
one another... I hope I make myself clear, lady?

Oops, wrong mailing list. Near enough.

Python is not magic. It's not that it "knows" when I reach the end of
a statement; it simply rules that line ends correspond to statement
ends unless ordered otherwise. It has been told that the reduction of
indentation level is a lexer token. Rick, do you realize that you have
to spoon-feed the interpreter with spaces/tabs when other interpreters
just KNOW to drop back an indentation level when you close a brace?

</troll>

I simply need to make sure that the interpreter and I have the same
understanding of the code. It will then work correctly. There's
nothing special about one syntax or another, they're all just
communication from my brain to a CPU, and different syntaxes are
suited to different tasks. There's nothing inherently wrong with:

right_length = len(x) > 5, < 20

being a valid way of expressing a double condition. It puts the query
first and the bounds after it, so hey, it's justifiably sane. (No, I'm
not advocating adding this. It's just for argument's sake.) Whatever
you're writing with, you need to have the same rules in your head as
the compiler/interpreter uses; beyond that there's a huge amount of
personal preference (I quite like braces, myself, but many others
don't) and only a relatively small amount of actual logic (use ASCII
mathematical symbols to represent mathematical operations).

ChrisA
 
C

Chris Angelico

py> 1 + 3 * 4
should ALWAYS equal 16!

With parenthesis only used for grouping:
py> a + (b*c) + d

Which seems like the most consistent approach to me.

Oh yes, absolutely consistent. Consistency. It's a CR 1/2 monster
found on page 153 of the 3.5th Edition Monster Manual.

Let's see. By your principle, we should evaluate the ** before the - in:

2**-1

Have fun.

ChrisA
 
R

Rick Johnson

Rick, do you realize that you have
to spoon-feed the interpreter with spaces/tabs when other interpreters
just KNOW to drop back an indentation level when you close a brace?

Yes. And significant white space is my favorite attribute of Python
source code. But the difference here is like night and day. While your
getting bogged down playing "match-the-brackets", i'm writing code and
being productive. I don't need to put any mental effort into pressing
the Enter+Tab keys. On the contrary, you must constantly break your
mental focus to "corral" the braces, and the sad part is, you're still
formatting your code like mine (with tabs and newlines!) however your
simultaneously juggling superfluously archaic syntax! Why Chris? WHY?
I simply need to make sure that the interpreter and I have the same
understanding of the code. It will then work correctly. There's
nothing special about one syntax or another,

I agree in the sense of: "to each his own". However. There are methods
of writing code that are more productive, and methods that are less
productive, and your emotive agenda of defending such nostalgic
pedantry is quite self-defeating.
they're all just
communication from my brain to a CPU, and different syntaxes are
suited to different tasks. There's nothing inherently wrong with:

right_length = len(x) > 5, < 20

Agreed. I wish we had one language. One which had syntactical
directives for scoping, blocks, assignments, etc, etc...

BLOCK_INDENT_MARKER -> \t
BLOCK_DEDENT_MARKER -> \n
STATEMENT_TERMINATOR -> \n
ASSIGNMENT_OPERATOR -> :=
CONDITIONAL_IF_SPELLING -> IF
CONDITIONAL_ELSE_SPELLING -> EL
....
(I quite like braces, myself, [...] and only a relatively small
amount of actual logic.

So you have a penchant for confinement and an aversion to logic? Hmm,
interesting!
 
T

Thomas Jollans

Agreed. I wish we had one language. One which had syntactical
directives for scoping, blocks, assignments, etc, etc...

BLOCK_INDENT_MARKER -> \t
BLOCK_DEDENT_MARKER -> \n
STATEMENT_TERMINATOR -> \n
ASSIGNMENT_OPERATOR -> :=
CONDITIONAL_IF_SPELLING -> IF
CONDITIONAL_ELSE_SPELLING -> EL
...

You must be joking.

In C, for example, it is possible to "create your own language" by going

#define IF(cond) if (cond) {
#define ELSE } else {
#define ELIF(cond) } else if (cond) {
#define ENDIF }


and so on. There's a reason nobody does it.
 
I

Ian Kelly

Which of the two comparisons is done first anyway?
"In the face of ambiguity, refuse the temptation to guess."

I would consider that a pro, not a con, because the C-like way is much
worse in this regard. Using operator chaining, is "1 < 2 < 3"
equivalent to:

1 < 2 and 2 < 3 # assuming left-to-right evaluation order for "and"
2 < 3 and 1 < 2

The former seems pretty obvious to me (since it more closely matches
the original syntax) and also turns out to be correct, but more to the
point, most of the time it really doesn't matter which is evaluated
first. It's only relevant if:

a) your comparison operator has side-effects (bad programmer! bad!); or
b) one of the comparisons is significantly faster than the other --
but since usually both comparisons will be of the same type (e.g. both
comparing two numbers), this is also a corner case.

On the other hand, using the C-like interpretation, is "1 < 2 < 3"
equivalent to:

(1 < 2) < 3
1 < (2 < 3)

I would guess the former is more common, but I really have no basis
for that guess beyond some experience with languages that use this
syntax. I can see no particular advantages to either interpretation
and can certainly imagine that some languages might choose the latter
instead. Moreover, the distinction actually matters in this case,
because the first expression is true (at least in Python) while the
second is false.

I will take ambiguity that is mostly unimportant over ambiguity that
is critical to the meaning of the expression any day of the week.

Cheers,
Ian
 

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,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top