?

Log in

No account? Create an account

Thu, Apr. 28th, 2005, 10:39 pm
Control flow

Here is a form of control flow not supported in any language I've used:
loopa: while x:
    do stuff
    if something:
        continue loopb
loopb: while y:
    do other stuff
    if something else:
        continue loopa
To be sane, when loopa exits here control flow should naturally jump to after loopb instead of falling through to it, otherwise it's rather like duff's device.

I have to admit I've never really wanted this functionality in any code I've written, but it's a form of cleaning up the call stack and jumping not supported by break/continue/return/raise.

Yesterday I wrote code with the following structure, and corresponding comment, which I find hilarious but is probably a bit on the obscure side:
for p in q:
    do stuff
    for a in b:
        if f(a):
            break
    else:
        # Niklaus Wirth can bite me
        continue
    do more stuff

Fri, Apr. 29th, 2005 06:16 am (UTC)
eqe

The indentation of your else clause is screwy.

What does a continue add when it's the last statement in the loop body? Unless it's supposed to continue the outer loop, which is just freaky.

Fri, Apr. 29th, 2005 07:17 am (UTC)
randallsquared

The else clause is on the for loop, not the if textually just above it.

Fri, Apr. 29th, 2005 06:49 am (UTC)
node

Don't continuations (in ML, or Scheme) give you that kind of control flow? An example in Python.

Fri, Apr. 29th, 2005 07:36 am (UTC)
darius

Yeah, though you don't need continuations here, just tail-call elimination, unless you really really want to use a while loop.

Fri, Apr. 29th, 2005 08:48 am (UTC)
(Anonymous): Double negation is unpythonic.

This shoud do:
for p in q:
    do stuff
    if any(f(a) for a in b):
        do more stuff

where any can pre-python2.5 be defined as (see Guido's Weblog entry (http://www.artima.com/weblogs/viewpost.jsp?thread=98196)):
def any(l):
    for i in l:
        if i: 
            return True
    return False

No more double negation (by break/continue). Not so funny, but pythonic.
Note that you can use filter(f, b) instead of any, but this will evaluate all f(a) for a in b instead of stopping at the first positive.

Yes, I am bereft of humor. ;)

Cheers,
Andre

Fri, Apr. 29th, 2005 04:18 pm (UTC)
bramcohen: Re: Double negation is unpythonic.

There are a few of these things in a row, each of which can be logically removed separately, so forcing the later section to get indented throws off the logic quite a bit.

I do agree that the following would look cleaner in this case:
for p in q:
    do stuff
    if any(f(a) for a in b):
        continue
    do more stuff

But my real code example was actually more complicated that this - it's actually looking for two things to hit, not just one, and has a counter in there. So your simplification wouldn't work on my real code, but it does on the simplified example.

Fri, Apr. 29th, 2005 02:00 pm (UTC)
misterajc

Bram sez:

Here is a form of control flow not supported in any language I've used:
[...]
I have to admit I've never really wanted this functionality in any code I've written [...]


Coincidence?

Andrew

Fri, Apr. 29th, 2005 04:19 pm (UTC)
bramcohen

Probably not, but noone's ever explained to me what co-routines do (not in a way I understand, anyway), and I was thinking maybe I reinvented those.
coroutines - (Anonymous) - Expand
Co-routines - (Anonymous) - Expand

Fri, Apr. 29th, 2005 02:15 pm (UTC)
etrepum

I've definitely written funky loops like this before, using try:except: might be "better" depending on how twisted your loops are. I'd probably have written this case like Andre's suggestion, though.

This is probably my worst offense:
dct = {....}
while True:
    for key, condition in dct.iteritems():
        if condition() is None:
            continue
        # modifying a dict being iterated.. but breaking
        del dct[key]
        break
    else:
        # break out of the while loop
        break


It's done this way because the "condition" functions have side-effects (if returning something other than None) that can effect whether other condition functions will trigger, so this chunk of code waits until the system reaches equilibrium... either by exhausting the dict, or going through a pass where it doesn't change.

It's obviously not the optimal algorithm for doing this, but it's the best way I've found to express it in this case given the density of doing it another way, the size of the data set, and Python's behavior.

...

On a related note, have you been following any of the PEP-340 discussions on python-dev? There's some interesting possibilities in there.

Fri, Apr. 29th, 2005 04:32 pm (UTC)
bramcohen

That does look fairly sketchy, not because it's hard to read but because it's fragile - it would be easy to forget the rule later and break it.

The wonkiest thing to read in your example is the 'if condition() is None:', because it seems to imply that condition() has no side effects, which is very misleading.

I haven't been following those discussions, although I've only even seen finally used for cleanup, so I'm all in favor of cleaner, less dangerous syntax which gets rid of it.

Fri, Apr. 29th, 2005 04:37 pm (UTC)
nvioli

seems like you could do this pretty easily using recursion in scheme or similar (no loops needed):

(define (function1 arg1)
(begin
(dostuff)
(if (something)
(function2 (increment arg1))
(output1))))

(define (function2 arg2)
(begin
(domorestuff)
(if (somethingelse)
(function1 (increment arg2))
(output2))))

forgive me if that's not what you meant. i'm pretty new at this stuff.

Fri, Apr. 29th, 2005 04:38 pm (UTC)
nvioli

sorry about the indentation. i don't know how to do fixed width fonts.
int - (Anonymous) - Expand

Tue, May. 3rd, 2005 04:08 pm (UTC)
(Anonymous): Control flow

I've come across one case where this control structure would have been useful. It's in the "almost inverse algorithm" for calculating polynomial inverses (used in elliptic curve cryptography, see http://citeseer.ist.psu.edu/schroeppel95fast.html).

One case in 20 years isn't really enough to justifying adding this to a programming language, I think :-)

It's easy enough to simulate by adding another outer loop, anyway.

-- David Hopwood

Tue, May. 10th, 2005 02:54 pm (UTC)
(Anonymous): coroutines

These are coroutines... you can do them in any language.

Sun, Jun. 26th, 2005 07:45 pm (UTC)
bramcohen: Re: coroutines

Not quite coroutines - coroutines yield instead of returning, keeping their stack information between calls.

Tue, Jun. 21st, 2005 02:50 pm (UTC)
(Anonymous): C is your friend

Yeah, they're evil blah blah:

loopa: while(1) {
         do_something();
         if(something) goto loopb;
}
loopa: while(1) {
         do_something_else();
         if(something_else) goto loopa;
}


The point is, its possible.

Sun, Jun. 26th, 2005 07:46 pm (UTC)
bramcohen: Re: C is your friend

you have a typo - you meant loopb in that second label.

Yeah, forgot about good ol' goto. C gets away with that by forcing all local variables to be scoped for the entire function.

Tue, Jun. 21st, 2005 02:55 pm (UTC)
(Anonymous): Perhaps I misunderstand

What's the difference between your loops and this code?

def loopb():
    while y:
    do other stuff
    if something else:
        yield

#loop a
while x:
    do stuff
    if something:
        loopb()


--p3d0

Sun, Jun. 26th, 2005 07:47 pm (UTC)
bramcohen: Re: Perhaps I misunderstand

In that example loopb isn't calling loopa.

Tue, Jun. 21st, 2005 06:39 pm (UTC)
(Anonymous)

not supported in any language I've used


Hmm you must be using broken languages. You have my sympathy. Any language at all with tail-recursion can express this example e.g. lua, scheme, ml, haskell, etc. Here is an example in ocaml (http://caml.inria.fr)


let x = ref true in
let y = ref true in
let something = ref true in
let somethingelse = ref true in

let do_stuff () =
  (* do whatever to x and y *)
  print_string "doing stuff\n" in

let do_otherstuff () =
  print_string "doing otherstuff\n" in

let rec loopa () = 
  if !x then
    begin
      do_stuff ();
      if !something then loopb () else loopa ()
    end

and loopb () =
  if !y then
    begin
      do_otherstuff ();
      if !somethingelse then loopa () else loopb ()
    end

in loopa ()

Sun, Jun. 26th, 2005 07:48 pm (UTC)
bramcohen

That also depends on different variable scoping rules than are in languages I've used. I tend to think of the way your preferred languages carry around variable scope as fairly broken though, since it's a lot less clear where variables are coming from.

Point taken, however.
scoping - (Anonymous) - Expand

Tue, Jun. 21st, 2005 07:13 pm (UTC)
(Anonymous)

for p in q:
    do stuff
    flag = False
    for a in b:
        if f(a):
            flag = True
            break
    if flag == False:
        continue
    do more stuff

Sun, Jun. 26th, 2005 07:50 pm (UTC)
bramcohen

That only supports one entry point from the first section to the second. But it can be extended using two checks to be more general, and the two code sections can both use yield to support full-blown coroutines.

Wed, Jun. 22nd, 2005 01:04 am (UTC)
(Anonymous): Java (named continues)

Just to point it out, Java can do this:

public class test {
	public static void main(String args[]) {
		int x = 20, y = 20;
		
		loopa: while (x > 0) {
			System.out.println("X = " + x);

			x -= 2;

			if (y == (x + 4)) {
				while (y > 0) {
					--y;
					System.out.println("Y = " + y);
					if (x >= y) {
						continue loopa;
					}
				}
			}
		}
	}
}


Derek

Sun, Jun. 26th, 2005 07:51 pm (UTC)
bramcohen: Re: Java (named continues)

That only supports one entry point into the second section of code, based on where it's included.

The two sections can both be pulled out and put in an if/else to fix that problem, although that gets fairly ugly.
(Deleted comment)

Sun, Jun. 26th, 2005 07:55 pm (UTC)
bramcohen

Yeah, that approach works, although it's a bit ugly. In Python you can support full-blown coroutines with that approach using yield, which is even uglier, but at least is understandable, unlike coroutines in C.