Quantcast

a short essay about programming

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

a short essay about programming

kirby urner-4
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
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

John Zelle
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=[hidden email] [edu-sig-bounces+john.zelle=[hidden email]] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 2:45 AM
To: [hidden email]
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
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

John Zelle
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=[hidden email] [edu-sig-bounces+john.zelle=[hidden email]] on behalf of John Zelle [[hidden email]]
Sent: Saturday, April 21, 2012 8:11 AM
To: kirby urner; [hidden email]
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=[hidden email] [edu-sig-bounces+john.zelle=[hidden email]] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 2:45 AM
To: [hidden email]
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
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

A. Jorge Garcia
I always teach my kids about sentinel loops. I have them avoid breaking loops at all costs! What about do loop untils?
--
Sent from my Android phone with K-9 Mail. Please excuse my brevity.

John Zelle <[hidden email]> wrote:
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@python.org [edu-sig-bounces+john.zelle=wartburg.edu@python.org] on behalf of John Zelle [[hidden email]]
Sent: Saturday, April 21, 2012 8:11 AM
To: kirby urner; [hidden email]
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 yo u 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@python.org [edu-sig-bounces+john.zelle=wartburg.edu@python.org] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 2:45 AM
To: [hidden email]
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 elegan t, 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 guess ed 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 frie nd. 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
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig


Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig


Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig

_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

kirby urner-4
In reply to this post by John Zelle
On Sat, Apr 21, 2012 at 6:11 AM, John Zelle <[hidden email]> wrote:
> 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.
>


Ah now there's a good rule of thumb maybe.

Whatever sentinel condition let you into the loop should not be
retested inside the loop.

That sounds cogent.

A related but somewhat contrary mental picture is:  only let live fish
into the fish tank, but some die once already inside the tank.

Are there cases where you need a preview, aren't ready to exit yet, so
need to re-test inside?

> 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

Pausing here:  I see this style quite often, of parens crammed right
up against the while keyword.  I

'm always worried when I see that in beginner Python that they're
imagining while is a callable, and their feeding it some kind of
argument.

Same with if:  I'll get if(x>5):

You're an experienced Pythonista though...

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

Or is there sometimes?  I'm thinking this is an excellent rule of
thumb of the kind that needs to be broken.

Python in particular is interesting because it tries to stay simple -
economical in providing control over flow.

Other languages gorge themselves on all imaginable kinds of loop
syntax, bulking up their keyword vocabulary in the process sometimes
(e.g. 'until').

I agree your examples look more elegant and economical than mine
featuring a re-test.

> 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.")
>

This is where the while statement's option else suite proves useful,
if you want to execute a block precisely because the while condition
has flipped.

I tend to find this useful in conjunction with 'break' though,
exploding my earlier hypothesis that while loops should have only one
entrance and one exit.

while guess != secret:  # front door
    if tries > allowed:
        print("Bummer")
        break  # back door
    tries += 1
    guess = input ("Guess? ")
else:
    print("Congratulations!")

Most public spaces have multiple exits, including a fire escape for
emergencies.  We haven't even touched on try: / except: as a way to
escape a while loop.

Mostly I just want these considerations to rise to the surface and
ruffle the surface awareness of the beginner mind programmer.

This is being a good thread for that.

Kirby
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

kirby urner-4
In reply to this post by kirby urner-4
... another useful contribution to this thread.

---------- Forwarded message ----------
From: Richard Pattis <[hidden email]>
Date: Sat, Apr 21, 2012 at 6:53 PM
Subject: I'm not allowed to post
To: kirby urner <[hidden email]>


Feel free to post this for me, which got returned. Probably
because I use a mail alias and never tried to post before.

Rich


I replied to Kirby privately, and he suggested I post to the listserv,
so I have. Because UCI is starting to teach Python next year, I might
be on the listserv (more than you want) over the summer as I learn
Python and how to program in it and teach it: a tall order for a few
months. As Charles McCabe (SF Examiner) said, "Any clod can
have the facts; having opinions is an art." - rep


Kirby,

I'm a long time Java teacher (heck, I started teaching Fortran in
1976) who will soon be teaching Python. We (here at UC Irvine)
are switching to a 3 quarter introduction to programming sequence
all in Python (and then on to Java and C++ for upper division courses,
although Python could leak upwards).

I've been subscribing to edu-sig Python for a while and appreciate
your efforts (and have learned from them).

I'm a big fan in Java of teaching beginners only
for(;;) -I call it for-ever- and if/break termination (although some
colleagues accuse me of teaching machine language in Java).

I like forever if/break for a few reasons, among them are I
teach ifs right before looping, so students get a chance to
immediately use ifs; I think it is easier to state the
condition in which to break instead of the condition in which
to continue (although we discuss how to compute one from
the other by DeMorgan's laws later); there is only one looping
form, so students aren't faced with the up-front decision of what
kind of loop to use; and finally, they can fully write the loop and
its body and then worry about termination later (unlike a while
loop, where syntactically the first thing you must write is the
continuation test -although some computer scientists would
consider this a feature, not a bug).

So my students don't have to think of which Java loop to use:
while or do/while. They code everything (early in class) using
forever if/break, although later I illustrate the semantics of Java's
other looping forms, using forever if/break (which they are familiar
with), tell them if the pattern fits, do the transformation to simplify
their code, and if you do this enough, later in the course you will
write the "right" pattern first, without thinking about the transformation.

I'm not a big fan of continue here. I conceptualize this as an
N=1/2 loop (sentinel loops are also of this form) and am happy
with forever if/break; The question I'd have for a while loop, is
where does guess come from the first time the test is evaluated;
if the answer is some initialization that is not equal to secret, I'd
say yuck: guess should store the user's guesses only, not some crazy
value to make things work the first time the loop is entered.

Another reason I don't like continue is that it isn't local; you have to
"go around the loop again to exit".; and will you exit the next time
around, it doesn't jump out at me (same, but a little less so, for your
exit = True code in the later example; more soon). With break, you recognize
termination and perform the action immediately (like return). Also,
you have to have the condition stated twice in your final code, in two
opposite forms (guess != secret) and also (guess == secret); confusing
and double yuck.

Now in Java you could write this as a do/while,but you still have the
"repeat the test in two opposite forms" problem.

In your summary

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


I agree with this, and argue this should be your loop form always
for beginners. I also require (for most of the quarter) at most one
break per loop.

> However, as soon as I make that rule I can
> think of good reasons to break it.


Yes, I believe you should break that rule, not continue to use  it

In the next code you set exit = True then continue so the next
iteration terminates
the loop, which I think is also bad because of non-locality. You are
terminating indirectly all over the loop. I tell my students since
loop termination
is what the loop is all about, it should be obvious where you are
terminating and why. I start by making them put their break statements in
comment sandwiches, so they stand out:

   //////
   break;
   //////

OK, so how would I write your loop to match its semantics, but be
as simple as possible?  Before showing my code, I dislike the fact
that your code is impolite: it always says "try again" on a wrong guess,
but then sometimes doesn't let the user try again. So I'll fix this "feature".

In fact, your code infinite loops when allowed = 1 (because tries,
which is initialized
at 1, is incremented before it is tested == allowed); in other cases
it is impolite
and gives the user one fewer try than they should get; so I'll fix
those bugs (you
could fix them by setting tries to 0, which I think is right, because
the user hasn't
tried yet; I'd increment tries right before or after the user enters a
new guess: more
locality).

Moral, never post code to the internet: the bas**rds will tear you apart).
Here is my Java in Python code. I'm assuming that the user always gets
at least one guess.

allowed = 5
tries   = 0

while true:
   guess = int(input("Guess?: ")
   tries += 1

   if guess == secret:
       print("You guessed it!")
       break;                                        # terminate loop

   if tries == allowed:
       print("You've maxed out")
       break;                                        # terminate loop

   print("Nope, try again...")

one fewer variable (no exit; Sartre had good programming intuition)
two fewer (18%) statements in the loop body. Now, we can still do some things to
reduce the size (but possibly increase the complexity of understanding
this code, and requiring knowledge of an "advance" python feature)

while true:
  guess = int(input("Guess?: ")
  tries += 1
  if guess == secret or tries == allowed
      break                                          # terminate loop

  print("Nope, try again...")
print("You guessed it" if guess == secret else "You've maxed out")

Note the last statement cannot be if tries != allowed, because both
might be true,
but the secret test does what you want.

What I like about this solution is it localizes the termination test (one
break) and the final disposition of the game (one "you won/lost"
statement). What I dislike about it (I'm no Pygmalion) is the uncoupling
between the termination and printing, and it has "redundant " tests
(now three, instead of two: one of my big complaints in the code you
started this discussion with).

I admit, I didn't follow any "simple to state" rules when writing this code.
My rule of thumb is not to minimize the number of loop exits but to
minimize the complexity, and I'm not totally sure what that means.

Feel free to rip me for any mistakes I've made, but now I'm off to think about
"private" variables in Python classes.

Rich Pattis

[ trimmed off copy of Kirby's essay -- Kirby ]
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

John Zelle
Hi All,

Interesting thread. Sorry about the extraneous parentheses in my initial post, I've just come off a semester of Java and am reflexively putting parentheses around my conditions.

One last thought. While I sometimes use the infinite loop form (while True:) I think the explicit loop condition is almost always preferable because of its value in clarifying what the loop is about and simplifying reasoning about the code. If the loop condition is C and there are no breaks, then you know after the loop that not C must be the case. When the loop condition is True (or a Boolean flag) or the loop contains breaks, you must mentally execute the loop in order to know the state of the computation immediately following.

Of course, the real bonus is when the explicit condition, C, is accompanied by a loop invariant, INV,  that describes what the loop is actually trying to accomplish. Together not C and INV should be sufficient to convince yourself (and others reading your code) that your loop has accomplished exactly what you wanted it to.

--John

John Zelle, PhD
Professor of Computer Science
Wartburg College


________________________________________
From: edu-sig-bounces+john.zelle=[hidden email] [edu-sig-bounces+john.zelle=[hidden email]] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 9:12 PM
To: [hidden email]
Subject: Re: [Edu-sig] a short essay about programming

... another useful contribution to this thread.

---------- Forwarded message ----------
From: Richard Pattis <[hidden email]>
Date: Sat, Apr 21, 2012 at 6:53 PM
Subject: I'm not allowed to post
To: kirby urner <[hidden email]>


Feel free to post this for me, which got returned. Probably
because I use a mail alias and never tried to post before.

Rich


I replied to Kirby privately, and he suggested I post to the listserv,
so I have. Because UCI is starting to teach Python next year, I might
be on the listserv (more than you want) over the summer as I learn
Python and how to program in it and teach it: a tall order for a few
months. As Charles McCabe (SF Examiner) said, "Any clod can
have the facts; having opinions is an art." - rep


Kirby,

I'm a long time Java teacher (heck, I started teaching Fortran in
1976) who will soon be teaching Python. We (here at UC Irvine)
are switching to a 3 quarter introduction to programming sequence
all in Python (and then on to Java and C++ for upper division courses,
although Python could leak upwards).

I've been subscribing to edu-sig Python for a while and appreciate
your efforts (and have learned from them).

I'm a big fan in Java of teaching beginners only
for(;;) -I call it for-ever- and if/break termination (although some
colleagues accuse me of teaching machine language in Java).

I like forever if/break for a few reasons, among them are I
teach ifs right before looping, so students get a chance to
immediately use ifs; I think it is easier to state the
condition in which to break instead of the condition in which
to continue (although we discuss how to compute one from
the other by DeMorgan's laws later); there is only one looping
form, so students aren't faced with the up-front decision of what
kind of loop to use; and finally, they can fully write the loop and
its body and then worry about termination later (unlike a while
loop, where syntactically the first thing you must write is the
continuation test -although some computer scientists would
consider this a feature, not a bug).

So my students don't have to think of which Java loop to use:
while or do/while. They code everything (early in class) using
forever if/break, although later I illustrate the semantics of Java's
other looping forms, using forever if/break (which they are familiar
with), tell them if the pattern fits, do the transformation to simplify
their code, and if you do this enough, later in the course you will
write the "right" pattern first, without thinking about the transformation.

I'm not a big fan of continue here. I conceptualize this as an
N=1/2 loop (sentinel loops are also of this form) and am happy
with forever if/break; The question I'd have for a while loop, is
where does guess come from the first time the test is evaluated;
if the answer is some initialization that is not equal to secret, I'd
say yuck: guess should store the user's guesses only, not some crazy
value to make things work the first time the loop is entered.

Another reason I don't like continue is that it isn't local; you have to
"go around the loop again to exit".; and will you exit the next time
around, it doesn't jump out at me (same, but a little less so, for your
exit = True code in the later example; more soon). With break, you recognize
termination and perform the action immediately (like return). Also,
you have to have the condition stated twice in your final code, in two
opposite forms (guess != secret) and also (guess == secret); confusing
and double yuck.

Now in Java you could write this as a do/while,but you still have the
"repeat the test in two opposite forms" problem.

In your summary

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


I agree with this, and argue this should be your loop form always
for beginners. I also require (for most of the quarter) at most one
break per loop.

> However, as soon as I make that rule I can
> think of good reasons to break it.


Yes, I believe you should break that rule, not continue to use  it

In the next code you set exit = True then continue so the next
iteration terminates
the loop, which I think is also bad because of non-locality. You are
terminating indirectly all over the loop. I tell my students since
loop termination
is what the loop is all about, it should be obvious where you are
terminating and why. I start by making them put their break statements in
comment sandwiches, so they stand out:

   //////
   break;
   //////

OK, so how would I write your loop to match its semantics, but be
as simple as possible?  Before showing my code, I dislike the fact
that your code is impolite: it always says "try again" on a wrong guess,
but then sometimes doesn't let the user try again. So I'll fix this "feature".

In fact, your code infinite loops when allowed = 1 (because tries,
which is initialized
at 1, is incremented before it is tested == allowed); in other cases
it is impolite
and gives the user one fewer try than they should get; so I'll fix
those bugs (you
could fix them by setting tries to 0, which I think is right, because
the user hasn't
tried yet; I'd increment tries right before or after the user enters a
new guess: more
locality).

Moral, never post code to the internet: the bas**rds will tear you apart).
Here is my Java in Python code. I'm assuming that the user always gets
at least one guess.

allowed = 5
tries   = 0

while true:
   guess = int(input("Guess?: ")
   tries += 1

   if guess == secret:
       print("You guessed it!")
       break;                                        # terminate loop

   if tries == allowed:
       print("You've maxed out")
       break;                                        # terminate loop

   print("Nope, try again...")

one fewer variable (no exit; Sartre had good programming intuition)
two fewer (18%) statements in the loop body. Now, we can still do some things to
reduce the size (but possibly increase the complexity of understanding
this code, and requiring knowledge of an "advance" python feature)

while true:
  guess = int(input("Guess?: ")
  tries += 1
  if guess == secret or tries == allowed
      break                                          # terminate loop

  print("Nope, try again...")
print("You guessed it" if guess == secret else "You've maxed out")

Note the last statement cannot be if tries != allowed, because both
might be true,
but the secret test does what you want.

What I like about this solution is it localizes the termination test (one
break) and the final disposition of the game (one "you won/lost"
statement). What I dislike about it (I'm no Pygmalion) is the uncoupling
between the termination and printing, and it has "redundant " tests
(now three, instead of two: one of my big complaints in the code you
started this discussion with).

I admit, I didn't follow any "simple to state" rules when writing this code.
My rule of thumb is not to minimize the number of loop exits but to
minimize the complexity, and I'm not totally sure what that means.

Feel free to rip me for any mistakes I've made, but now I'm off to think about
"private" variables in Python classes.

Rich Pattis

[ trimmed off copy of Kirby's essay -- Kirby ]
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

Mark Engelberg-2
John Zelle wrote:
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")


I mostly find John Zelle's version to be more elegant, but I dislike that the line:
guess = int(input("Guess? "))
occurs in two places.

In general, we should be discouraging writing the same line twice.  What if you want to change the prompt?  What if you want to create more complex error checking for the input.  Are you going to remember to change it in both places?

One reasonable option is to break this out into a separate getGuess() function.  You'd still end up with two calls to the getGuess() function, but at least the logic of getting the guess could easily be changed in one place.

This begs the question, though, as to whether it is possible to rework the code to only have one line which gets the guess, without involving continue, break, or while(True). 

I don't believe there is a way.  If Python had tail recursion, one way to rewrite this that satisfies all those constraints would be:

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

This would be bad form for Python, though.

One other thought about while/continue/break.  I am always thinking about the fact that we're training kids for the languages and programming styles that will emerge over the next 10+ years.  To better understand the future we're preparing them for, I spend a lot of time studying emerging languages, looking for clues about what styles will best "future-proof" my students.

In the case of while loops, I think it's instructive to look at Scala, a language that is currently being hailed as the most plausible successor to Java.  Scala doesn't have break or continue at all.  The idea is that if you have a loop that requires a break, it is far clearer to make that loop into a separate helper function, and use return instead of break.  So for example, looking at Kirby's code:

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

you'd instead write it as:

def repeatUntilSecretIsGuessed():
  while True: 
    guess = int(input("Guess?: ")
    if guess == secret:
        print("You guessed it!")
        return           
# It's a bit easier to understand this code because we see it completely exits the function here, not  just the while loop
    else:
        print("Nope, try again...")

In this example, the distinction seems a little silly, but I would argue that the vast majority of while loops are, semantically speaking, "returning a value".  They do this by setting up some accumulator variable before the while loop, and then pounding on the variable, changing it each time through the while loop.  It can take a bit of analysis to determine which is the variable(s) you care about, and what it contains at the time you break out of a loop.  By breaking the loop into a separate function, and actually returning the value you care about with a return statement, the code becomes much easier to understand.

So for example, let's say you want to keep looping until you get a guess from 1 to 10.

Standard way (using while True and break) would be something like this:

while True:
  guess = int(input("Guess a number from 1 to 10? "))
  if (guess < 1 or guess > 10):
     print ("Try again")
  else:
     break
# at this point we continue our code, and we know guess contains a number from 1 to 10

Better way:

def getNumberFrom1To10():
  while True:
    guess = int(input("Guess a number from 1 to 10? "))
    if (guess < 1 or guess > 10):
       print ("Try again")
    else:
       return guess
# Now, it's really obvious what is the value that is being "set" by the while loop.

In any case, whether you prefer Kirby's while True version or John's version which requires asking for a guess both before the loop and inside the loop, the main idea here is to get kids thinking each time they have a loop, especially a loop that involves break, whether maybe the code would be better if they factored out the loop into a separate function.

--Mark

_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

david-2
In reply to this post by John Zelle
Yes, interesting thread. I've been thinking about this statement:

"If the loop condition is C and there are no breaks, then you know after the loop that not C must be the case."

What if condition C includes an expression like (datetime.datetime.now().hour) == x)?

What if condition C is (not q.empty()), where q is a queue.Queue object fed by other threads?

I think there has to be some constraints on C for that statement to be correct. Something like "and C does not refer to volatile attributes nor variables and doesn't contain method calls whose results vary over time" or something like that.

I guess that's why I think using "while True:" with "break" is Ok in many cases.

David H

-----Original Message-----
From: "John Zelle" <[hidden email]>
Sent: Saturday, April 21, 2012 11:13pm
To: "kirby urner" <[hidden email]>, "[hidden email]" <[hidden email]>
Subject: Re: [Edu-sig] a short essay about programming

Hi All,

Interesting thread. Sorry about the extraneous parentheses in my initial post, I've just come off a semester of Java and am reflexively putting parentheses around my conditions.

One last thought. While I sometimes use the infinite loop form (while True:) I think the explicit loop condition is almost always preferable because of its value in clarifying what the loop is about and simplifying reasoning about the code. If the loop condition is C and there are no breaks, then you know after the loop that not C must be the case. When the loop condition is True (or a Boolean flag) or the loop contains breaks, you must mentally execute the loop in order to know the state of the computation immediately following.

Of course, the real bonus is when the explicit condition, C, is accompanied by a loop invariant, INV,  that describes what the loop is actually trying to accomplish. Together not C and INV should be sufficient to convince yourself (and others reading your code) that your loop has accomplished exactly what you wanted it to.

--John

John Zelle, PhD
Professor of Computer Science
Wartburg College


________________________________________
From: edu-sig-bounces+john.zelle=[hidden email] [edu-sig-bounces+john.zelle=[hidden email]] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 9:12 PM
To: [hidden email]
Subject: Re: [Edu-sig] a short essay about programming

... another useful contribution to this thread.

---------- Forwarded message ----------
From: Richard Pattis <[hidden email]>
Date: Sat, Apr 21, 2012 at 6:53 PM
Subject: I'm not allowed to post
To: kirby urner <[hidden email]>


Feel free to post this for me, which got returned. Probably
because I use a mail alias and never tried to post before.

Rich


I replied to Kirby privately, and he suggested I post to the listserv,
so I have. Because UCI is starting to teach Python next year, I might
be on the listserv (more than you want) over the summer as I learn
Python and how to program in it and teach it: a tall order for a few
months. As Charles McCabe (SF Examiner) said, "Any clod can
have the facts; having opinions is an art." - rep


Kirby,

I'm a long time Java teacher (heck, I started teaching Fortran in
1976) who will soon be teaching Python. We (here at UC Irvine)
are switching to a 3 quarter introduction to programming sequence
all in Python (and then on to Java and C++ for upper division courses,
although Python could leak upwards).

I've been subscribing to edu-sig Python for a while and appreciate
your efforts (and have learned from them).

I'm a big fan in Java of teaching beginners only
for(;;) -I call it for-ever- and if/break termination (although some
colleagues accuse me of teaching machine language in Java).

I like forever if/break for a few reasons, among them are I
teach ifs right before looping, so students get a chance to
immediately use ifs; I think it is easier to state the
condition in which to break instead of the condition in which
to continue (although we discuss how to compute one from
the other by DeMorgan's laws later); there is only one looping
form, so students aren't faced with the up-front decision of what
kind of loop to use; and finally, they can fully write the loop and
its body and then worry about termination later (unlike a while
loop, where syntactically the first thing you must write is the
continuation test -although some computer scientists would
consider this a feature, not a bug).

So my students don't have to think of which Java loop to use:
while or do/while. They code everything (early in class) using
forever if/break, although later I illustrate the semantics of Java's
other looping forms, using forever if/break (which they are familiar
with), tell them if the pattern fits, do the transformation to simplify
their code, and if you do this enough, later in the course you will
write the "right" pattern first, without thinking about the transformation.

I'm not a big fan of continue here. I conceptualize this as an
N=1/2 loop (sentinel loops are also of this form) and am happy
with forever if/break; The question I'd have for a while loop, is
where does guess come from the first time the test is evaluated;
if the answer is some initialization that is not equal to secret, I'd
say yuck: guess should store the user's guesses only, not some crazy
value to make things work the first time the loop is entered.

Another reason I don't like continue is that it isn't local; you have to
"go around the loop again to exit".; and will you exit the next time
around, it doesn't jump out at me (same, but a little less so, for your
exit = True code in the later example; more soon). With break, you recognize
termination and perform the action immediately (like return). Also,
you have to have the condition stated twice in your final code, in two
opposite forms (guess != secret) and also (guess == secret); confusing
and double yuck.

Now in Java you could write this as a do/while,but you still have the
"repeat the test in two opposite forms" problem.

In your summary

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


I agree with this, and argue this should be your loop form always
for beginners. I also require (for most of the quarter) at most one
break per loop.

> However, as soon as I make that rule I can
> think of good reasons to break it.


Yes, I believe you should break that rule, not continue to use  it

In the next code you set exit = True then continue so the next
iteration terminates
the loop, which I think is also bad because of non-locality. You are
terminating indirectly all over the loop. I tell my students since
loop termination
is what the loop is all about, it should be obvious where you are
terminating and why. I start by making them put their break statements in
comment sandwiches, so they stand out:

   //////
   break;
   //////

OK, so how would I write your loop to match its semantics, but be
as simple as possible?  Before showing my code, I dislike the fact
that your code is impolite: it always says "try again" on a wrong guess,
but then sometimes doesn't let the user try again. So I'll fix this "feature".

In fact, your code infinite loops when allowed = 1 (because tries,
which is initialized
at 1, is incremented before it is tested == allowed); in other cases
it is impolite
and gives the user one fewer try than they should get; so I'll fix
those bugs (you
could fix them by setting tries to 0, which I think is right, because
the user hasn't
tried yet; I'd increment tries right before or after the user enters a
new guess: more
locality).

Moral, never post code to the internet: the bas**rds will tear you apart).
Here is my Java in Python code. I'm assuming that the user always gets
at least one guess.

allowed = 5
tries   = 0

while true:
   guess = int(input("Guess?: ")
   tries += 1

   if guess == secret:
       print("You guessed it!")
       break;                                        # terminate loop

   if tries == allowed:
       print("You've maxed out")
       break;                                        # terminate loop

   print("Nope, try again...")

one fewer variable (no exit; Sartre had good programming intuition)
two fewer (18%) statements in the loop body. Now, we can still do some things to
reduce the size (but possibly increase the complexity of understanding
this code, and requiring knowledge of an "advance" python feature)

while true:
  guess = int(input("Guess?: ")
  tries += 1
  if guess == secret or tries == allowed
      break                                          # terminate loop

  print("Nope, try again...")
print("You guessed it" if guess == secret else "You've maxed out")

Note the last statement cannot be if tries != allowed, because both
might be true,
but the secret test does what you want.

What I like about this solution is it localizes the termination test (one
break) and the final disposition of the game (one "you won/lost"
statement). What I dislike about it (I'm no Pygmalion) is the uncoupling
between the termination and printing, and it has "redundant " tests
(now three, instead of two: one of my big complaints in the code you
started this discussion with).

I admit, I didn't follow any "simple to state" rules when writing this code.
My rule of thumb is not to minimize the number of loop exits but to
minimize the complexity, and I'm not totally sure what that means.

Feel free to rip me for any mistakes I've made, but now I'm off to think about
"private" variables in Python classes.

Rich Pattis

[ trimmed off copy of Kirby's essay -- Kirby ]
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig


_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

A. Jorge Garcia
Give me loop invariants or give me death!
A. Jorge Garcia
Applied Math & CS
http://shadowfaxrant.blogspot.com
http://www.youtube.com/calcpage2009
--
Sent from my Android phone with K-9 Mail. Please excuse my brevity.

[hidden email] wrote:
Yes, interesting thread. I've been thinking about this statement:

"If the loop condition is C and there are no breaks, then you know after the loop that not C must be the case."

What if condition C includes an expression like (datetime.datetime.now().hour) == x)?

What if condition C is (not q.empty()), where q is a queue.Queue object fed by other threads?

I think there has to be some constraints on C for that statement to be correct. Something like "and C does not refer to volatile attributes nor variables and doesn't contain method calls whose results vary over time" or something like that.

I guess that's why I think using "while True:" with "break" is Ok in many cases.

David H

-----Original Message-----
From: "John Zelle" <[hidden email]>
Sent: Saturday, April 21, 2012 11:13pm
To: "kirby urner" <[hidden email]>, "[hidden email]" <[hidden email]>
Subject: Re: [Edu-sig] a short essay about programming

Hi All,

Interesting thread. Sorry about the extraneous parentheses in my initial post, I've just come off a semester of Java and am reflexively putting parentheses around my conditions.

One last thought. While I sometimes use the infinite loop form (while True:) I think the explicit loop condition is almost always preferable because of its value in clarifying what the loop is about and simplifying reasoning about the code. If the loop condition is C and there are no breaks, then you know after the loop that not C must be the case. When the loop condition is True (or a Boolean flag) or the loop contains breaks, you must mentally execute the loop in order to know the state of the computation immediately following.

Of course, the real bonus is when the explicit condition, C, is accompanied by a loop invariant, INV, that describes what the loop is actually trying to accomplish. Together not C and INV should be sufficient to convince yourself (and others reading your code) that your loop has accomplished exactly what you wanted it to.

--John

John Zelle, PhD
Professor of Computer Science
Wartburg College




From: edu-sig-bounces+john.zelle=wartburg.edu@python.org [edu-sig-bounces+john.zelle=wartburg.edu@python.org] on behalf of kirby urner [[hidden email]]
Sent: Saturday, April 21, 2012 9:12 PM
To: [hidden email]
Subject: Re: [Edu-sig] a short essay about programming

... another useful contribution to this thread.

---------- Forwarded message ----------
From: Richard Pattis <[hidden email]>
Date: Sat, Apr 21, 2012 at 6:53 PM
Subject: I'm not allowed to post
To: kirby urner <[hidden email]>


Feel free to post this for me, which got returned. Probably
because I use a mail alias and never tried to post before.

Rich


I replied to Kirby privately, and he suggested I post to the listserv,
so I have. Because UCI is starting to teach Python next year, I might
be on the listserv (more than you want) over the summer as I learn
Python and how to program in it and teach it: a tall order for a few
months. As Charles McCabe (SF Examiner) said, "Any clod can
have the facts; having opinions is an art." - rep


Kirby,

I'm a long time Java teacher (heck, I started teaching Fortran in
1976) who will soon be teaching Python. We (here at UC Irvine)
are switching to a 3 quarter introduction to programming sequence
all in Python (and then on to Java and C++ for upper division courses,
although Python could leak upwards).

I've been subscribing to edu-sig Python for a while and appreciate
your efforts (and have learned from them).

I'm a big fan in Java of teaching beginners only
for(;;) -I call it for-ever- and if/break termination (although some
colleagues accuse me of teaching machine language in Java).

I like forever if/break for a few reasons, among them are I
teach ifs right before looping, so students get a chance to
immediately use ifs; I think it is easier to state the
condition in which to break instead of the condition in which
to continue (although we discuss how to compute one from
the other by DeMorgan's laws later); there is only one looping
form, so students aren't faced with the up-front decision of what
kind of loop to use; and finally, they can fully write the loop and
its body and then worry about termination later (unlike a while
loop, where syntactically the first thing you must write is the
contin uation test -although some computer scientists would
consider this a feature, not a bug).

So my students don't have to think of which Java loop to use:
while or do/while. They code everything (early in class) using
forever if/break, although later I illustrate the semantics of Java's
other looping forms, using forever if/break (which they are familiar
with), tell them if the pattern fits, do the transformation to simplify
their code, and if you do this enough, later in the course you will
write the "right" pattern first, without thinking about the transformation.

I'm not a big fan of continue here. I conceptualize this as an
N=1/2 loop (sentinel loops are also of this form) and am happy
with forever if/break; The question I'd have for a while loop, is
where does guess come from the first time the test is evaluated;
if the answer is some initialization that is not equal to secret, I'd
say yuck: guess sh ould store the user's guesses only, not some crazy
value to make things work the first time the loop is entered.

Another reason I don't like continue is that it isn't local; you have to
"go around the loop again to exit".; and will you exit the next time
around, it doesn't jump out at me (same, but a little less so, for your
exit = True code in the later example; more soon). With break, you recognize
termination and perform the action immediately (like return). Also,
you have to have the condition stated twice in your final code, in two
opposite forms (guess != secret) and also (guess == secret); confusing
and double yuck.

Now in Java you could write this as a do/while,but you still have the
"repeat the test in two opposite forms" problem.

In your summary

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


I agree with this, and argue this should be your loop form always
for beginners. I also require (for most of the quarter) at most one
break per loop.

> However, as soon as I make that rule I can
> think of good reasons to break it.


Yes, I believe you should break that rule, not continue to use it

In the next code you set exit = True then continue so the next
iteration terminates
the loop, which I think is also bad because of non-locality. You are
terminating indirectly all over the loop. I tell my students since
loop termination
is what the loop is all about, it should be obvious where you are
terminating and why. I start by making them put their break statements in
comment sandwiches, so they stand out:

//////
break;
//////

OK, so how would I write your loop to match its semantics, but be
as simple as possible? Before showing my code, I dislike the fact
that your code is impolite: it always says "try again" on a wrong guess,
but then sometimes doesn't let the user try again. So I'll fix this "feature".

In fact, your code infinite loops when allowed = 1 (because tries,
which is initialized
at 1, is incremented before it is tested == allowed); in other cases
it is impolite
and gives the user one fewer try than they should get; so I'll fix
those bugs (you
could fix them by setting tries to 0, which I think is right, because
the user hasn't
tried yet; I'd increment tries right before or after the user enters a
new guess: more
locality).

Moral, never post code to the internet: the bas**rds will tear you apart).
Here is my Java in Python code. I'm assuming that the user always gets
at least one guess.

allowed = 5
tries = 0

while true:
guess = int(input("Guess?: ")
tries += 1

if guess == secret:
print("You guessed it!")
break; # terminate loop

if tries == allowed:
print("You've maxed out")
break; # terminate loop

print("Nope, try again...")

one fewer variable (no exit; Sartre had good programming intuition)
two fewer (18%) statements in the loop body. Now, we can still do some things to
reduce the size (but possibly increase the complexity of understanding
this code, and requiring knowledge of an "advance" python feature)

while true:
guess = int(input("Guess?: ")
tries += 1
if guess == secret or tries == allowed
break # terminate loop

print("Nope, try again...")
print("You guessed it" if guess == secret else "You've maxed out")

Note the last statement cannot be if tries != allowed, because both
might be true,
but the secret test does what you want.

What I like about this solution is it localizes the termination test (one
break) and the final disposition of the game (one "you won/lost"
statement). What I dislike about it (I'm no Pygmalion) is the uncoupling
between the termination and printing, and it has "redundant " tests
(now three, instead of two: one of my big complaints in the code you
started this discussion with).

I admit, I didn't follow any "simple to state" rules when writing this code.
My rule of thumb is not to minimize the number of loop exits but to
minimize the complexity, and I'm not totally sure what that means.

Feel free to rip me for any mistakes I've made, but now I'm off to think about
"private" variables in Python classes.

Rich Pattis

[ trimmed off copy of Kirby's essay -- Kirby ]


Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig


Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig




Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig

_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: a short essay about programming

Paul Gries
In reply to this post by Mark Engelberg-2
Several people in this thread have mentioned repeat-until loops, and much of the discussion here seems to be about how to approximate them in Python. In readability dreamland:

tries_left = 5 # Must be > 0

repeat:
    guess = int(input("Guess? "))
    tries -= 1
until guess == secret or tries_left == 0

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

A Racket friend of mine likes to point out the many times in our intro course where we state a problem by saying "do something until X", and then spend time figuring out how to shoehorn the problem into a while loop.

(Aside: Mark, I find your while-function approach to be thought provoking.)

-Paul Gries

On 2012-04-22, at 4:44 PM, Mark Engelberg wrote:

> John Zelle wrote:
> 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")
>
>
> I mostly find John Zelle's version to be more elegant, but I dislike that the line:
> guess = int(input("Guess? "))
> occurs in two places.
>
> In general, we should be discouraging writing the same line twice.  What if you want to change the prompt?  What if you want to create more complex error checking for the input.  Are you going to remember to change it in both places?
>
> One reasonable option is to break this out into a separate getGuess() function.  You'd still end up with two calls to the getGuess() function, but at least the logic of getting the guess could easily be changed in one place.
>
> This begs the question, though, as to whether it is possible to rework the code to only have one line which gets the guess, without involving continue, break, or while(True).  
>
> I don't believe there is a way.  If Python had tail recursion, one way to rewrite this that satisfies all those constraints would be:
>
> def repeatUntilSecretIsGuessed():
>   guess = int(input("Guess? "))
>   if (guess == secret):
>     print("You guessed it")
>   else:
>     print("Nope, try again")
>     repeatUntilSecretIsGuessed()
>
> This would be bad form for Python, though.
>
> One other thought about while/continue/break.  I am always thinking about the fact that we're training kids for the languages and programming styles that will emerge over the next 10+ years.  To better understand the future we're preparing them for, I spend a lot of time studying emerging languages, looking for clues about what styles will best "future-proof" my students.
>
> In the case of while loops, I think it's instructive to look at Scala, a language that is currently being hailed as the most plausible successor to Java.  Scala doesn't have break or continue at all.  The idea is that if you have a loop that requires a break, it is far clearer to make that loop into a separate helper function, and use return instead of break.  So for example, looking at Kirby's code:
>
> while True:  # no ifs ands or buts
>    guess = int(input("Guess?: ")
>    if guess == secret:
>        print("You guessed it!")
>        break
>    else:
>        print("Nope, try again...")
>
> you'd instead write it as:
>
> def repeatUntilSecretIsGuessed():
>   while True:  
>     guess = int(input("Guess?: ")
>     if guess == secret:
>         print("You guessed it!")
>         return            
> # It's a bit easier to understand this code because we see it completely exits the function here, not  just the while loop
>     else:
>         print("Nope, try again...")
>
> In this example, the distinction seems a little silly, but I would argue that the vast majority of while loops are, semantically speaking, "returning a value".  They do this by setting up some accumulator variable before the while loop, and then pounding on the variable, changing it each time through the while loop.  It can take a bit of analysis to determine which is the variable(s) you care about, and what it contains at the time you break out of a loop.  By breaking the loop into a separate function, and actually returning the value you care about with a return statement, the code becomes much easier to understand.
>
> So for example, let's say you want to keep looping until you get a guess from 1 to 10.
>
> Standard way (using while True and break) would be something like this:
>
> while True:
>   guess = int(input("Guess a number from 1 to 10? "))
>   if (guess < 1 or guess > 10):
>      print ("Try again")
>   else:
>      break
> # at this point we continue our code, and we know guess contains a number from 1 to 10
>
> Better way:
>
> def getNumberFrom1To10():
>   while True:
>     guess = int(input("Guess a number from 1 to 10? "))
>     if (guess < 1 or guess > 10):
>        print ("Try again")
>     else:
>        return guess
> # Now, it's really obvious what is the value that is being "set" by the while loop.
>
> In any case, whether you prefer Kirby's while True version or John's version which requires asking for a guess both before the loop and inside the loop, the main idea here is to get kids thinking each time they have a loop, especially a loop that involves break, whether maybe the code would be better if they factored out the loop into a separate function.
>
> --Mark
> _______________________________________________
> Edu-sig mailing list
> [hidden email]
> http://mail.python.org/mailman/listinfo/edu-sig

_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Loading...