You are viewing bramcohen

Sun, May. 31st, 2009, 09:52 pm
Maybe goto isn't so bad after all.

I've used this pattern a lot over the last few days:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            break
    else:
        continue
    break
else:
    return
do stuff with eggs and jello


People familiar with my code will find this amount of complex control flow unsurprising. But the reasoning here is different from my past practices. In the past I always optimized to have as few lines of code as possible, regardless of how upside down and backwards it made everything be organized, but that could be done much better in this case like so:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            do stuff with eggs and jello
            return


There are two reasons I find this construct objectionable. First is that it leads to a lot of code having a lot of indentation (the 'do stuff' is in many cases dozens of lines) which isn't good for readability. Second is that the natural flow of the code is 'scan for the thing I want, and then do something with it' which is the flow of the structure I've been using. I suppose one could write it like this:

def my_little_func():
    for eggs in spam:
        for jello in eggs:
            if jello is the one I want:
                return eggs, jello
    return None, None

eggs, jello = my_little_func()
if eggs is None:
    return
do stuff with eggs and jello


But that's hardly elegant and forces the flow to jump around in terms of the visual layout of the code, which goes against what I'm trying to accomplish.

At the risk of starting a religious war, I'd like to point out that the most readable and versatile way of doing what I want is like so:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            goto x
return
x: do stuff with eggs and jello


But short of that the following may be the best option currently available. I might switch to it:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            after_func(eggs, jello)
            return

def after_func(eggs, jello):
    do stuff with eggs and jello


But it still doesn't look as nice as the version with a goto.

Mon, Jun. 1st, 2009 05:21 am (UTC)
misterajc

I think your second example does express "scan for the thing I want, and then do something with it" pretty well. As for the indentation problem, have you considered setting your editor to display tabs as two or three characters wide rather than four?

Mon, Jun. 1st, 2009 05:35 am (UTC)
cratermoon

two words: map & closure

Mon, Jun. 1st, 2009 06:33 am (UTC)
allter

That's one reason why I like Perl - it has more structured solution: the last operator.

CHECK: {
for $eggs ( @spam ) {
for $jello ( @$eggs ) {
if ( $jello is the one I want ) {
last CHECK;
} } }
return; # check failed
}
do stuff with eggs and jello;

P.S. Don't know how to easily format that.

Mon, Jun. 1st, 2009 07:27 am (UTC)
ciphergoth

This is the Perl feature I have most wished for in Python by a long way. Still, I'm not sure this comes up often enough to worry about, and the solutions presented here are generally good enough.

Sat, Jun. 6th, 2009 03:22 am (UTC)
bramcohen

Yeah, that's basically the structure I'm talking about.

Mon, Jun. 1st, 2009 06:34 am (UTC)
muzyka_sfer: religious propaganda

ah,. you are a victim of religious propaganda, too..

The simple fact is that the motto "goto is considered harmful" was propagated by an extremist, mr Djikstra, and in reaction to the previous programming practices, which they labelled as "spagetti code". What came after the war was the "structured programming paradigm", which technically manifested itself in building operators for the creation of subroutines and functions into programming languages.

The fighters of this Reformation, however, did not realize that "goto" itself is sufficient for an elegant minimalistic way of subroutine creation, and that this technique avoids overhead plus restrictions on arguments and return results etc.
Actually, the question of utility of "goto" as a language operator is completely independent of the question of need to structure larger programs - but historically, falsely, they became inseparable. Goto got cursed, and remained one of the strongest taboos of programming: as usual, humanity was saved from itself by forcing unto it the ways to do things, and heretics were damned so strongly that to this very day every bloody author on every book on programming feels obliged to appologize profusely when mentioning the "goto" operator, exactly once in the whole text and not to remember the damn thing afterwards, ever.
The witchhunt continued for decades, I have a university publication from 1994, "researching" (!!) the ways goto can be eliminated from programs - at the expense of creating complicated set of moves totally upturning logic and readability.

In fact, there existed a different position even during time of Religious Wars, much more moderate that that of extremist Djikstra. You might be surprised to learn that one of the moderates' names was Donald Knuth, who published an article advocating the use of the operator when needed. As far as I remember, he even cited examples of proper and even beautiful uses of "goto".

What is even less known, at least in this context, is that Knuth offered his own paradigm in competition to Structured Programming. Many people heard the name of it, but few, amazingly few (considering the simplicity of the approach) know what that term means: Literate Programming.
L.P. is not "a documentation tool" - it is a programming paradigm, that allows structuring without the use of built-in functions/subroutines for human readability only.
L.P. basically is (a) writing programs in pseudocode, whereupon arbitrary phrases in a human language become precise operators of a "meta-language" created at the moment of programming
(b) and a way to provision for programming in the order of thinking, i.e. not as machine instructions demand, but the way humans logically do it.

Having hit upon the idea and tried it, Knuth wrote of the feeling of relief from the "structured programming" practices and proclaimed that L.P. was the true way to program. Regrettably, not that many noticed.

After Knuth's first Web tools for L.P. others were created, simplifying the concept and detangling ot both from the TeX and interdependence with a particular programming language, the most accessible one is "noweb" by Ramsey.

To return to the question of "goto" and finish with it, let me tell also that even enemies of "goto" during those Wars admitted that it works faster (at least for C implementations) than built-in subroutines/functions, the latter besides restricting the programmer's thinking involve unneccessary overhead.
The use of built-in functions is justified for recursion, for instance, when implicit shuffling behind the scenes is used for a good purpose, but can be avoided when subdivision is created simply not to lose the thread when thinking.

And the idea of structuring the program into precise equivalent of subroutines/functions with goto statements (which produces a clear, readable layout as well) is very, very true, while being totally unknown to the programming masses today.

So o hell with extremist Djikstra.
As far as I am concerned, Goto is considered helpful

Mon, Jun. 1st, 2009 12:45 pm (UTC)
g253: Re: religious propaganda

Agreed. And I think Bram's example is a very good illustration. I've always liked "elegant" code, and always thought this whole goto bashing was blown out of proportion. It shouldn't be overused, and sometimes it's not the best construct, but gotos are a perfectly legitimate method of flow control.

Mon, Jun. 1st, 2009 11:00 am (UTC)
hukuma

If you want fewest lines of code:

for (eggs,jello) in [(e,j) for e in spam for j in e if j is the one I want][:1]:
do stuff with eggs and jello


Mon, Jun. 1st, 2009 02:15 pm (UTC)
agthorr

Here's another variation:

def iterspam(spam):
    for eggs in spam:
        for jello in eggs:
            yield eggs, jello

for eggs, jello in iterspam(spam):
    if i_want(jello):
        break
else:
    return
do stuff with eggs and jello

Tue, Jun. 2nd, 2009 06:54 am (UTC)
manuzhai

That one is nice. Also, for the not-so-bad case, you may want to inverse the nested condition to make it continue if it doesn't match what you're looking for. So

for i in range(5):
    if i == 3:
        do(i)
        things(i)
        and(i)
        some(i)
        more(i)


becomes

for i in range(3):
    if i != 3:
        continue
    do(i)
    things(i)
    and(i)
    some(i)
    more(i)

Mon, Jun. 1st, 2009 03:52 pm (UTC)
dojothemouse

What on Earth is wrong with the last example?

It has the bonus of informing future coders that after_func requires eggs & jello both, so don't lose the eggs.

Sat, Jun. 6th, 2009 03:24 am (UTC)
bramcohen

It's probably the best variant, for some reason it didn't occur to me until I was writing the post though.

Tue, Jun. 2nd, 2009 12:02 am (UTC)
adamthebastard

Whenever the topic of goto comes up I always like to point out that the Linux kernel has gotos. They are used where they provide a useful speed increase or aid in readability. I can't find a reference for the speed increase but over on KernelTrap.org there is a post about a discussion on the LKML about Using goto In Kernel Code where Linus (and others) explain that goto can make code clearer. If it's good enough for people who write a half way decent operating system then it's good enough for me.