[Edu-sig] a short essay about programming

John Zelle john.zelle at wartburg.edu
Sat Apr 21 16:18:24 CEST 2012


Oops, I think I have an off by one error in my last example. I would change the conditional at the bottom to:

if guess == secret:
    print("You guessed it!")
else:
    print("You maxed out.")

That's clearer anyway.


John Zelle, PhD
Professor of Computer Science
Wartburg College


________________________________________
From: edu-sig-bounces+john.zelle=wartburg.edu at python.org [edu-sig-bounces+john.zelle=wartburg.edu at python.org] on behalf of John Zelle [john.zelle at wartburg.edu]
Sent: Saturday, April 21, 2012 8:11 AM
To: kirby urner; edu-sig at python.org
Subject: Re: [Edu-sig] a short essay about programming

Kirby,

There are some nice thoughts here that I don't really disagree with, but your code examples don't use the while conditions well. If you put a condition on the loop, there should be no reason to retest the same condition inside the loop. Think of the loop condition as a guard, inside the loop it is true, outside the loop it has become false.

That suggests the more elegant (in my eyes) way to write your first example as a sort of sentinel loop:

guess = int(input("Guess? "))
while(guess != secret):  // as long as the user didn't get it, get another guess
    print("Nope, try again")
    guess = int(input("Guess? "))
// Here we know the condition is false
print("You guessed it")

There's no reason for the re-test of the loop condition to either break or continue.

This applies to the second example as well, but a post-loop conditional will still be required to figure out why the loop quit:

allowed = 5

guess = int(input("Guess? "))
tries = 1

while guess != secret and tries < allowed:   //user gets to try again
     print("Nope, try again")
     guess = int(input("Guess? "))
     tries += 1

if tries <= allowed:
    print("You guessed it")
else:
    print("You've maxed out.")

I like having the loop condition telling us exactly what the loop accomplishes. Using something like an "exit" or "done" variable obscures that because it does not announce what is required in order for the loop to be done.

Of course the cost of this style is the repeated input statement, but a priming read is a standard part of a sentinel loop, and both examples are shorter than the versions that retest or assign a conditional inside the loop.

John Zelle, PhD
Professor of Computer Science
Wartburg College


________________________________________
From: edu-sig-bounces+john.zelle=wartburg.edu at python.org [edu-sig-bounces+john.zelle=wartburg.edu at python.org] on behalf of kirby urner [kirby.urner at gmail.com]
Sent: Saturday, April 21, 2012 2:45 AM
To: edu-sig at python.org
Subject: [Edu-sig] a short essay about programming

A common "error" (not too serious) that I see in
beginning Python (and no doubt other languages,
but Python is the one I'm teaching), is having a
while condition that appears to put a lid on things,
but then the flow all leaks away through break
statements, such that the "front door" condition
is never revisited.

while guess != secret:
    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
        break
    else:
        print("Nope, try again...")

What's messed up about the above code is you
never really go back to the top in the case
where you'd get to leave.  Rather, you exit by
the back door, through break.

So in that case, wouldn't have been simpler and
more elegant, even more "correct" (dare I say it)
to have gone:

while True:  # no ifs ands or buts
    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
        break
    else:
        print("Nope, try again...")

I see lots of heads nodding, and that could be
the end of the matter, but then a next question
arises:  wouldn't this also be a more correct
solution?:

while guess != secret:
    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
        continue  # instead of break
    else:
        print("Nope, try again...")

We're back to having a variable while condition,
not a boolean constant, but this time we actually
exit by means of it, thanks to continue or...

while guess != secret:
    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
    else:
        print("Nope, try again...")

... thanks to no continue.  This last one is getting
a thumbs up, but then I'd pause here and say
"continue" can be easier on the eyes.  It's
unambiguous where it takes you, in contrast
to having to scan on down the gauntlet, looking
for possibly other open doors.  "What happens
next" should not require scanning ahead too
far.  Help us not to get lost.  Be a civic-minded
coder.

I'm thinking of a programming style that advises
two things:

(a) if you use a while condition that's variable,
that's expected to change, then your goal should
be to always exit because of that, i.e. that should
be your only exit point.  Even if some other
criterion suggests exiting, you have the option
to flip that "lid" at the top, to crack that front
door, and bounce the ball out.

(b)  which is why 'continue' is your friend.  You
are showing the user where your 'break' statements
are, except you don't use "break" statements, as
you've given a non-constant condition, and your
aim is to make that your ONLY exit point.

In short:  never use break to exit a while loop
unless your condition is while True.

Instead, always flip the condition and exit
through the font door.

However, as soon as I make that rule I can
think of good reasons to break it.  The other
programmers in the room are shaking their
heads.  Won't we lose information, needed
elsewhere in the program, if we "artificially"
force a True condition to False.  Aren't we, in
effect, lying?  That concern could be addressed.
Keep all the info true, just treat the "lid
condition" (it "keeps a lid on it") as a flag.
Go ahead and remember the user's guess.

allowed = 5
tries = 1
exit = False

while not exit:  # no other way out

    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
        exit = True  # instead of break
        continue

    print("Nope, try again...")
    tries += 1
    if tries == allowed:
        print("You've maxed out")
        exit = True
        continue


I think we all understand the main issue:
writing reader-friendly code, and rules for doing
so.  There's something comforting about approaching
a while loop and knowing its not a leaky sieve,
riddled with back doors, maybe even an exit( )
time bomb.  But in the recursion world we want
a minimum of two exits usually:  when another
round is called for versus when we've "hit bottom".
Can we have it both ways?

Conclusions:

Lets not be too hasty with rules of thumb

and:

Lets keep the reader in mind when writing code.

Just because the interpreter knows to compute
the flow unambiguously, doesn't mean all ways
of writing it are equally reader-friendly.

What may seem a gratuitous gesture, an
unnecessary flourish, may actually promote
reader comprehension of your code, and that
should be a goal as much as satisfying the
interpreter.

Kirby
_______________________________________________
Edu-sig mailing list
Edu-sig at python.org
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
Edu-sig at python.org
http://mail.python.org/mailman/listinfo/edu-sig


More information about the Edu-sig mailing list