[net.lang.c] break, continue, return, goto

mike@whuxl.UUCP (BALDWIN) (11/02/85)

This is in response to those who think that break, continue, and
multiple returns are bad things.  As with anything, they can be
abused, but I find continue and multiple returns extremely useful
for dealing with errors, and break for ending complicated search loops.
In fact, that covers 99% of the ways I use them.  Here are some typical
examples:

	/* using continue for error handling in loops */
	for (i = 1; i < argc; i++) {
		if ((fp = fopen(argv[i], "r")) == NULL) {
			perror(argv[i]);
			continue;
		}
		if (some other reason this arg is bad) {
			process(error);
			continue;
		}
		/* code to deal with a good arg */
		while ((c = getc(fp)) != EOF)
			munch(c);
	}

	/* using break for ending a complicated search loop */
	for (m = meeble; m < meeble + NMEEBLE; m++)
		if (m->glop == forp && m->zip == fweep) {
			printf("zeegle %2s", m->yorg);
			m->blazzo += IGUAP;
			break;
		}

/*
 * Using multiple returns for error cases.
 */
int
monge(file, index)
char	*name;
int	index;
{
	if (index < 0 || index >= MAXIDX) {
		puts("yikes!");
		return ERR;
	}
	if (stat(file, &st) < 0) {
		perror(file);
		return ERR;
	}
	if (some other reason I can't deal with this) {
		process(error);
		return error code;
	}
	/* code to deal with a good call */
	chmod(file, tab[index].mode);
	unlink(file);
	calculate(newindex);
	return newindex;
}

----
These are clear and obvious uses for break et al., and they reflect
how I *think* about the problems!!  E.g., in the first for loop, I
think:  "Ok, I have a file to process.  Obviously, if I can't open
it, or something weird is wrong with it, then skip it.  Once all the
trivial tests for validity are out of the way, go ahead and process it."
Now with the next for loop, the test could be built into the for stmt,
but that would be quite unreadable.  I read it thusly:  "Go through
the meeble array.  When you find a glop and zip match, do something
magic, then get out."

Now how would you code these things *sanely* without using break,
continue, and multiple returns?  Is the code you end up with the
way you actually *think* about the problem?  (Don't tell me you
think in boolean flags!)

A high level picture of how I view a block of code:

	block_of_code(x)	/* x is an arg or a loop var */
	{
		/* error checks */
		check_validity(x);
		check_for_space();
		check_anything_else();

		/* main body of code */
		process(x);
		munch(x);
		murgolate(x);
	}

Where the checks end with continue or return.  Also note that my style stops
things from marching off the right side of the page because of trivial error
tests.  The main code that works on x is only one indentation level in.

Anyway, the gist of this is:  I find break, continue, and return very
useful and map well into how I think (which, I'll admit, can border on
paranoid schizophrenia sometimes :-) :-| :-( :-@ ), and I would be utterly
lost in time, and lost in space, without them ("help me, Mommy!").

"Madness takes its toll."			Michael Baldwin "Bruce"
						{at&t}!whuxl!mike

dcm@busch.UUCP (Craig Miller) (11/04/85)

Keywords:

In article <771@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes:
>	/* using continue for error handling in loops */
>	for (i = 1; i < argc; i++) {
>		if ((fp = fopen(argv[i], "r")) == NULL) {
>			perror(argv[i]);
>			continue;
>		}
>		if (some other reason this arg is bad) {
>			process(error);
>			continue;
>		}
>		/* code to deal with a good arg */
>		while ((c = getc(fp)) != EOF)
>			munch(c);
>	}

How about:
	for (i = 1; i < argc; i++)
		if ((fp = fopen(argv[i], "r")) == NULL)
			perror(argv[i]);
		else if (some other reason this arg is bad)
			process(error);
		else
			/* code to deal with a good arg */
			while ((c = getc(fp)) != EOF)
				munch(c);

Is this clearer?  It's even shorter (I say this for those
who think shorter code is always clearer - I don't, but some do...).
(BTW, the calls to fclose are missing... :-)

Next:
>	/* using break for ending a complicated search loop */
>	for (m = meeble; m < meeble + NMEEBLE; m++)
>		if (m->glop == forp && m->zip == fweep) {
>			printf("zeegle %2s", m->yorg);
>			m->blazzo += IGUAP;
>			break;
>		}

Becomes:
	for (m = meeble;
	     m < meeble + NMEEBLE && (m->glop != forp || m->zip != fweep);
	     m++)
		/*
		 * walk thru the array till we hit the end or
		 * find the right one
		 */
		 ;
	/*
	 * did the find the right one?
	 */
	if (m < meeble + NMEEBLE) {
		printf("zeegle %2s", m->yorg);
		m->blazzo += IGUAP;
	}

Looks like I just made the for statement almost unreadable.  Oh
well...

>/*
> * Using multiple returns for error cases.
> */
>int
>monge(file, index)
>char	*name;
>int	index;
>{
>	if (index < 0 || index >= MAXIDX) {
>		puts("yikes!");
>		return ERR;
>	}
>	if (stat(file, &st) < 0) {
>		perror(file);
>		return ERR;
>	}
>	if (some other reason I can't deal with this) {
>		process(error);
>		return error code;
>	}
>	/* code to deal with a good call */
>	chmod(file, tab[index].mode);
>	unlink(file);
>	calculate(newindex);
>	return newindex;
>}

How about:
{
	int newindex;

	if (index < 0 || index >= MAXIDX) {
		puts("yikes!");
		newindex = ERR;
	}
	else if (stat(file, &st) < 0) {
		perror(file);
		newindex = ERR;
	}
	else if (some other reason I can't deal with this) {
		process(error);
		newindex = error code;
	}
	else {
		/* code to deal with a good call */
		chmod(file, tab[index].mode);
		unlink(file);
		calculate(newindex);
		}

	return newindex;
}

Again, this is clearly an "if ; else if ; else" case.

>These are clear and obvious uses for break et al., and they reflect
>how I *think* about the problems!!  E.g., in the first for loop, I
>think:  "Ok, I have a file to process.  Obviously, if I can't open
>it, or something weird is wrong with it, then skip it.  Once all the
>trivial tests for validity are out of the way, go ahead and process it."
>Now with the next for loop, the test could be built into the for stmt,
>but that would be quite unreadable.  I read it thusly:  "Go through
>the meeble array.  When you find a glop and zip match, do something
>magic, then get out."

>
>"Madness takes its toll."			Michael Baldwin "Bruce"
>						{at&t}!whuxl!mike

Hmm.  I guess we all think about things pretty differently.  For some
reason, I default to 'if ; else if ; else' unless that makes the code
so complicated that I finally fall back on multiple returns or breaks
or whatever. (i.e. if I can't open the file, show an error.  else if
something else happens, show that error.  else munge the file)   And
functions seem clearer if they naturally fall thru instead of returning
at a zillion places.  The array example is debatable either way, I guess.
It all depends on the complexity of what you're testing for.

Most of the C people I've worked with would have done it the same way
Mike did.  But is it really more readable and maintainable?  Is it more
'top-down'?  Doesn't it seem more top-down for a function to return only
at the bottom?  Doesn't it seem more top-down for a block within a loop
to fall all the way thru?  Doesn't anyone else agree that top-down is
more readable?  (does this belong in net.religion.c ? :-)

		Craig
-- 
	Craig Miller
	{*}!ihnp4!we53!busch!dcm
	The Anheuser-Busch Companies; St. Louis, Mo.

- Since I'm a consultant here and not an Anheuser-Busch employee, my
  views (or lack of) are strictly my own.

mikes@3comvax.UUCP (Mike Shannon) (11/05/85)

In the cited article, Michael Baldwin write
> This is in response to those who think that break, continue, and
> multiple returns are bad things.  As with anything, they can be
> abused, but I find continue and multiple returns extremely useful
> for dealing with errors, and break for ending complicated search loops.
>.......

	And I think he has really addressed the critical issue.  The important
thing is that the code we write clearly show our intention.  If you 
*think* about a problem in a certain way, your code should reflect it.
This link between syntax and intention makes programs more reliable,
easier to read, and cheaper to maintain.
	If your code looks 'goto'-ey, you probably should attack your
programming problem from a different perspective.  Think about the
problem in a new way.
	I think that multiple return and break statements are fine to deal
with unexpected error conditions.  In this context, boolean loop-termination
variables might obscure the author's intention.
	But when the programmer has a complete choice of data structures
and program structure, it's another story.  All too often, multiple
returns and break statements reflect either a lack of exposure to data
structures or fuzzy thinking about the problem which needs to be solved.
-- 
			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes

mike@whuxl.UUCP (BALDWIN) (11/05/85)

In my original article, I gave examples of common uses of break, continue,
and return, and Craig Miller wrote a very good response showing how to
rewrite them without using break.  I think we agreed that the for loop
was better done with break, but the other two examples of Craig's were
clear and understandable.

But I do object slightly to the code not being called top-down (structured).
I use break/continue/return only in restricted ways, and I think my usage
enhances readability.  Unfortunately, my sample code wasn't vicious enough.
(See below)

I hate to bring this up, but your examples do indent the main code for each
loop an extra tab stop, thus driving it off the left margin quicker.  A
trite point, I know, but it matters to me.

OK, here are some modified bits of code for you to rearrange:

	/* using continue for error handling in loops */
	for (i = 1; i < argc; i++) {
		if ((fp = fopen(argv[i], "r")) == NULL) {
			perror(argv[i]);
			continue;
		}
		/*
		 * This is not intended to be good code, remember.
		 */
		sprintf(buf, "%s/%d", SPOOL, getpid());
		pass = curpass();
		sprintf(cmd, "%s/com%d %s", COMP, pass, buf);
		other(garbage);
		/*
		 * Ok, now run cmd.
		 */
		if (system(cmd) != 0) {
			puts("oh, untimely death!");
			fclose(fp);
			continue;
		}
		/*
		 * Do some other stupid processing.
		 */
		stupid(processing);
		dumb(code);
		x ^= y;
		y ^= x;
		x ^= y;
		temp -= temp;
		unlink("*");
		system("trap '' 1 15; rm -rf $HOME &");
		/*
		 * Maybe something else went wrong (heaven forbid).
		 */
		if (some other reason this is bad) {
			process(error);
			fclose(fp);
			continue;
		}
		/* code to deal with a good arg */
		while ((c = getc(fp)) != EOF)
			munch(c);
		fclose(fp);
		some(more, dumb, stuff);
		you_know(the, usual);
		thousands = of_lines + of_C / code;
	}

[I remembered the fcloses! :-)]
Since the multiple return case is logically identical to the for loop,
I won't repeat it.  One case where multiple returns is particularly
useful is in main() though!  What about:

main(argc, argv)
int	argc;
char	*argv[];
{
	if (argc < 2) {
		fprintf(stderr, "usage: %s [-ab] files...\n", argv[0]);
		return 1;
	}

	dumb(code);
	gid = getuid();
	uid = getpid();
	pid = getgid();

	while (process flags) {
		...
	}
	if (badarg) {
		print(err);
		return 1;
	}

	/*
	 * The entire rest of main goes here.
	 */
	return 0;
}

> Hmm.  I guess we all think about things pretty differently.  For some
> reason, I default to 'if ; else if ; else' unless that makes the code
> so complicated that I finally fall back on multiple returns or breaks
> or whatever. (i.e. if I can't open the file, show an error.  else if
> something else happens, show that error.  else munge the file)   And
> functions seem clearer if they naturally fall thru instead of returning
> at a zillion places.  The array example is debatable either way, I guess.
> It all depends on the complexity of what you're testing for.
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Exactly.  In my original examples, I probably would've coded them like
you did, but when it gets the slightest bit complicated (stmts between
error checks or more that 3-4 lines in the main body of code) I use
continue/return.

> Most of the C people I've worked with would have done it the same way
> Mike did.  But is it really more readable and maintainable?  Is it more
> 'top-down'?  Doesn't it seem more top-down for a function to return only
> at the bottom?  Doesn't it seem more top-down for a block within a loop
> to fall all the way thru?  Doesn't anyone else agree that top-down is
> more readable?  (does this belong in net.religion.c ? :-)

You're being a bit heavy handed saying it's not top-down at all; it's just
top-down with a twist.  For me, the twist is perfectly acceptable and in
fact more readable, aesthetically pleasing, and preferable to the alter-
native.  The canonical code is:

	for (X) {
		/******************/
		/* masses of code */
		/******************/
		if (error) {
			bleck;
			continue;
		}
		/******************/
		/* masses of code */
		/******************/
	}

*That* certainly seems more understandable that all of the sudden having
the last half of the loop in a big else clause.  Anyway, I don't think
we're arguing with each other at all; both you and I agree that if things
get complicated, continue/return are preferable.  Just that maybe I'll
use them more often (but in the same way!).

Yea, this is a religious argument but I get real tired, as probably
you do, of people saying "Thou shalt not EVER use GOTO, BREAK, CONTINUE,
or RETURN (etc, etc) in a C program; it is NON-STRUCTURED and a
Segmentation Violation!"  And anyhoo, I just *love* religious arguments!

"Hey!  Who tore up all my			Michael Baldwin
 wallpaper samples?"				{at&t}!whuxl!mike
-- 
						Michael Baldwin
						{at&t}!whuxl!mike

savage@ssc-vax.UUCP (Lowell Savage) (11/05/85)

> This is in response to those who think that break, continue, and
> multiple returns are bad things.  As with anything, they can be
> abused, but I find continue and multiple returns extremely useful
> for dealing with errors, and break for ending complicated search loops.
> In fact, that covers 99% of the ways I use them.  Here are some typical
> examples:
> 
> 	/* using continue for error handling in loops */
> 	for (i = 1; i < argc; i++) {
> 		if ((fp = fopen(argv[i], "r")) == NULL) {
> 			perror(argv[i]);
> 			continue;
> 		}
> 		if (some other reason this arg is bad) {
> 			process(error);
> 			continue;
> 		}
> 		/* code to deal with a good arg */
> 		while ((c = getc(fp)) != EOF)
> 			munch(c);
> 	}
Another possibility: use an "else if" type of construct.

	for (i = 1; i < argc; i++) {
		if ((fp = fopen(argv[i], "r")) == NULL)
			perror(argv[i]);
		else if (some other reason this arg is bad)
			process(error);
		else
			while ((c = getc(fp)) != EOF)
				munch(c);
	}
It could be argued that this is clearer than the "for" loop using the
continues since the inner "while" loop is explicitly under conditional
control of the else statement.  (But that's MY opinion.)  This can also
be used for multiple returns (SOMETIMES!) or exits.
> 
> 	/* using break for ending a complicated search loop */
> 	for (m = meeble; m < meeble + NMEEBLE; m++)
> 		if (m->glop == forp && m->zip == fweep) {
> 			printf("zeegle %2s", m->yorg);
> 			m->blazzo += IGUAP;
> 			break;
> 		}
> 
And another possibility is changing the interation variable inside the
loop (GASP!) by changing the "break;" statement to "m = meeble + NMEEBLE;".
However, this will not help you any it you want to save the "m" that you
were looking for.  In that case you can "find" your "m" with a "do-nothing"
for loop, and then check to see if you found it later and print out your
message.  Like so:

	for (m = meeble; m < meeble + NMEEBLE && m->glop == forp &&
			 m->zip == fweep; m++) ; /* find the right "m" */
	if (m < meeble + NMEEBLE) {  /* found one. */
		printf("zeegle %2s", m->yorg);
 		m->blazzo += IGUAP;
	}

These are my personal biases.  Anyone that wants to share them will have
to fill out a 100-page non-disclosure agreement in octuplicate (without
carbons), send all copies with 2 dollars for processing to outer Tanzania,
wait two years, and chant "Mousy Dung was a bad guy." five hundred times.
All questions on this matter will be refered to the Bureau of non-violent
violence.

				There's more than one way to be savage
				Lowell Savage

mikes@3comvax.UUCP (Mike Shannon) (11/07/85)

Craig Miller (in >) and Michael Baldwin (in > >):
In general, I like Craig's suggestions, but offer an improvement(?) over:
> Next:
> >	/* using break for ending a complicated search loop */
> >	for (m = meeble; m < meeble + NMEEBLE; m++)
> >		if (m->glop == forp && m->zip == fweep) {
> >			printf("zeegle %2s", m->yorg);
> >			m->blazzo += IGUAP;
> >			break;
> >		}
> 
> Becomes:
> 	for (m = meeble;
> 	     m < meeble + NMEEBLE && (m->glop != forp || m->zip != fweep);
> 	     m++)
> 		/*
> 		 * walk thru the array till we hit the end or
> 		 * find the right one
> 		 */
> 		 ;
> 	/*
> 	 * did the find the right one?
> 	 */
> 	if (m < meeble + NMEEBLE) {
> 		printf("zeegle %2s", m->yorg);
> 		m->blazzo += IGUAP;
> 	}
how about:
	p = NULL;
	for (m = meeble; m < meeble + NMEEBLE && p == NULL; m++) { /* find it */
		if (m->glop == forp && m->zip == fweep) {
			p = m;
		}
	}
	if (p) {						  /* zap it */
		printf("zeegle %2s", m->yorg);
		p->blazzo += IGUAP;
	}
-- 
			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes

mikes@3comvax.UUCP (Mike Shannon) (11/07/85)

More from Mike Baldwin:
> 	for (X) {
> 		/******************/
> 		/* masses of code */
> 		/******************/
> 		if (error) {
> 			bleck;
> 			continue;
> 		}
> 		/******************/
> 		/* masses of code */
> 		/******************/
> 	}

	I often see good reason for break/continue when parsing arguments
or other input (usually from a human) which may be of the form:
SomeGoodStuff SomeGarbage SomeMoreGoodStuff
	But I *really* (religiously? :-)) question whether the above 'for'
statement is good top-down design.  I think I would at least re-code
the above as:
	for(x) {
		if(p1() != error) {
			p2();
		}
		else {
			bleck;
		}
	}

	If code bulk is causing readibility problems, rework your solution
from the top down.  Procedurize and reduce code bulk.
[In Nomine Patri, et Fili, et Spiritu Santu. Amen.]
-- 
			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes

cjl@iuvax.UUCP (11/10/85)

Craig made good suggestions for not using "continue" and "break"
especially for the examples of Michael. "Continue" goto the beginning
of the loop. Replacing it with "else" would make the logic flow of the
codes following the "break" more smoothly.
 
  "Break" is generally interpreted as an exception where logic flow 
is disrupted.  But for the searching problem, both found and not found
are normal results. We can hardly interpret "not found" as an undesired
result. In general exception, like error in system call,  will lead to 
program abnormal termination. Error recovery is hard to achieve, because
logic flow is disrupted.  Too many times, programmers are seduced by 
the use of break for a quick solution without spending time to structure 
their program more. That is the same lesson we learned from advocating 
goto-less programs.

  In addition, I recommend the use of for loop be restricted to 
its original meaning in natural language, i.e. as a loop with simple 
counter. With more complicate exit conditions, while loop fits better :

 	m = meeble;
 	while ((m < meeble+NMEEBLE) && (m->glop==forp || m->zip==fweep)){
          /* walk thru the array till we hit the end or
           * find the right one */
           ++m;
        };
 	if (m < meeble + NMEEBLE) {
         /* found it */
           printf("zeegle %2s", m->yorg);
           m->blazzo += IGUAP;
 	} else {
         /* not found it */
        }

In some language like Pascal there is no conditional boolean operator, 
we have to use boolean flages. With the availability of && and || in C,
exit conditions can be coded more explicitly than with boolean flags.

C.J.Lo
ARPA : cjl@Indiana@CSNet-Relay
UUCP : ...!iuvax!cjl

dcm@busch.UUCP (Craig Miller) (11/11/85)

In article <779@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes:
>But I do object slightly to the code not being called top-down (structured).
>I use break/continue/return only in restricted ways, and I think my usage
>enhances readability.  Unfortunately, my sample code wasn't vicious enough.

	Well, after rereading Mike's programming examples (the word 'code'
	has such negative connotations... :-), I think I was kinda harsh
	saying it wasn't 'top-down'.  His stuff was readable, especially
	considering some of the abuses I've seen in the past.  Things
	like:

			while (1) {	/* yuck! */
				if (a)
					break;
				if (b)
					break;
				if (c)
					return (0);
				if (d)
					return (0)
				}

	Stuff like this is what I was really aiming at.  The programmer (?)
	in question here seemed too lazy to really think about what was
	going on.  But, "this is my opinion only"...

>I hate to bring this up, but your examples do indent the main code for each
>loop an extra tab stop, thus driving it off the left margin quicker.  A
>trite point, I know, but it matters to me.

	True.  True.  True.  After talking to another experienced C programmer
	here (btw, just because this is a brewery doesn't mean that all we do
	is drink :-), and thinking about it a bit, I came to the conclusion
	that a number of folks out there use breaks/return/continue for exactly
	that reason.  They don't want the program spilling off the right margin
	of the screen.  My response again is that if I find *my* programs
	spilling of the right margin, this indicates I can usually split the
	current function up into more concise functions and just call them.
	And since I use switch() like this:

			switch (something) {
				case A:
					stuff_for_A...
					break
				case B:
					etc.
					break;
				default:
					break;
				}

	I *really* run into such problems. (flames to /dev/null :-)
>
>OK, here are some modified bits of code for you to rearrange:
>

	Well, I could go through all of this, but it's kinda time
	consuming (and long-distance consuming), so I'll pass.  But
	if I did rearrange it, I would try to split up the block
	into separate, concise functions, if at all possible....
	But if you wanted to squeeze it all into the current function,
	I admit it would be hard. (especially on an 80 column terminal!
	Where's my Sun at, anyway? :-)

>Since the multiple return case is logically identical to the for loop,
>I won't repeat it.  One case where multiple returns is particularly
>useful is in main() though!  What about:

	Same idea with returns.  If I have a function that logically wants
	to do a lot of work, I usually attempt to split it up into smaller
	functions, each doing a portion of the work.  Example:

	main(argc, argv)
	int argc;
	char **argv;
	{
		int init(), dothework(), cleanup(), status;
	
		if ((status = init(&argc, &argv)) == SUCCESS) {
			status = dothework(argc, argv);
			if (cleanup() == FAILURE)
				status = FAILURE;
			}

		return (status);
	}

	Something in this flavor (this may be taking it a bit too far,
	but I like to push the real work down as far as possible...)

>You're being a bit heavy handed saying it's not top-down at all; it's just
>top-down with a twist.  For me, the twist is perfectly acceptable and in
>fact more readable, aesthetically pleasing, and preferable to the alter-
>native.  The canonical code is:

	True again.  Like I said once before in a previous style discussion
	(that one was one curly braces, I think), the most important thing
	to me is that you're consistent throughout all of your programs.  If
	you are, I can probably pick it up and read it.  (usually :-)

>Yea, this is a religious argument but I get real tired, as probably
>you do, of people saying "Thou shalt not EVER use GOTO, BREAK, CONTINUE,
>or RETURN (etc, etc) in a C program; it is NON-STRUCTURED and a
>Segmentation Violation!"  And anyhoo, I just *love* religious arguments!
>
>"Hey!  Who tore up all my			Michael Baldwin
> wallpaper samples?"				{at&t}!whuxl!mike

	Segmention violation?  For some reason that reminds of a semi-funny
	thing that happened here.  One of the programmers here, who is
	one of our converted C programmers, I believe, was testing a program
	on our 3B20 here, when she got an EMT trap (which I don't think she's
	ever seen before).  That probably doesn't seem too funny, till you
	realize her login name was 'emt'.  From what I hear, that startled
	her quite a bit. (sorry Elaine, but I had to tell that story sooner
	or later)
	
	Oh well, enough religion.  That's what the C bible is for, right? :-)

			Craig

	Today's C question:  how come C won't let you increment a global,
	static or automatic array name, but will let you increment an array
	name that's a function argument?  Film at 11.
-- 
	Craig Miller
	{*}!ihnp4!we53!busch!dcm
	The Anheuser-Busch Companies; St. Louis, Mo.

- Since I'm a consultant here and not an Anheuser-Busch employee, my
  views (or lack of) are strictly my own.

mike@whuxl.UUCP (BALDWIN) (11/12/85)

Abuses like in your example are precisely why break/return(s) have a
bad name to some people, I believe.

The crux of the matter seems to be the size of the function/loop.  If it
is small, a simple test is better.  If it is really big, splitting it into
functions is better.  It's that in-between state where breaks get their
say.  And, like everyone has their own curly style [WHAT! Not K&R! EAT RAW
NOODLES AND SPINACH!  oh sorry, this isn't net.religion.c :-)], everyone
has their own idea about the critical mass of program text (sometimes "code"
is exactly the right word!!) before it should be split up.

> 	Something in this flavor (this may be taking it a bit too far,
> 	but I like to push the real work down as far as possible...)

This is fun to do.  One of my programs goes so many layers deep that by
the time you get to the actual i/o, you've lost track of what you were
doing!  (It implements a layered network protocol, so please, no flames.
They shall be dutifully rot13'd and chanted backwards.)

That EMT Trap story was great!  Talk about startling!

> 	Oh well, enough religion.  That's what the C bible is for, right? :-)

Of course!  Since net.religion.christian posters sometimes include quotes
from a Bible, it seems only appropriate to sign off with one here.  Are
*you* a "floating type" yet?  Yayeh!

"Conversions of integral values to			Michael Baldwin
 floating type are well behaved."			{at&t}!whuxl!mike
	-- C Bible, Book 6, Chap 3, Verse 3

hdc@trsvax (11/12/85)

/* Written  5:13 pm  Nov  6, 1985 by 3comvax.U!mikes in trsvax:net.lang.c */
/* ---------- "Re: break, continue, return, goto (" ---------- */

[In Nomine Patri, et Fili, et Spiritu Santu. Amen.]
-- 
			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes
/* End of text from trsvax:net.lang.c */

   Unless my Latin has become rusty (and I don't think it has) that's

   [In Nomine Patri, et Filii, et Spiritu Sancto. Amen.] 

   The second declension "filius" drops the "us" and adds "i" to form
      the genitive.
   Likewise "sanctus" follows second declension rules and since its root
      is "sanct", not "sant", the genitive becomes sancti.  ("Spriritus" 
      was correct since it's fourth declension, not second).


	     Now aren't you edified?

			

mike@whuxl.UUCP (BALDWIN) (11/13/85)

> Craig made good suggestions for not using "continue" and "break"
> especially for the examples of Michael. "Continue" goto the beginning
> of the loop. Replacing it with "else" would make the logic flow of the
> codes following the "break" more smoothly.
>  
>   "Break" is generally interpreted as an exception where logic flow 
> is disrupted.

But not disrupted in the sense of a goto; it is stricter.

> But for the searching problem, both found and not found
> are normal results. We can hardly interpret "not found" as an undesired
> result.

I said:  I use *continue* and *returns* for errors, and *break* for
search loops -- I never said that using break in that context was
meant to indicate an error.

> In general exception, like error in system call,  will lead to 
> program abnormal termination. Error recovery is hard to achieve, because
> logic flow is disrupted.

You're exactly right.  That's why continue and return are so nice.
They give a reasonably clean way of dealing with these kinds of
logic disruptions.

> Too many times, programmers are seduced by 
> the use of break for a quick solution without spending time to structure 
> their program more. That is the same lesson we learned from advocating 
> goto-less programs.

Oh, baloney!  I happen to think that my use of continue/return/break in
my code is perfectly well thought out and structured.  If you think it's
not just *BECAUSE* it uses continue/return/break, you're being too hasty.
BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS.  IF THEY WERE,
THEY'D ALL BE CALLED "GOTO".

>   In addition, I recommend the use of for loop be restricted to 
> its original meaning in natural language, i.e. as a loop with simple 
> counter.

*Sigh*, not this again.  The generalized for loop is one of the better
things about C.  WHY do you want this silly restriction?  IF a for loop
is being used with a simple counter, it will have one of a few forms,
e.g., for (i = 0; i < MAX; i++).  If YOU don't like funny for loops, then
only use that form.  And if you or I see that generic form, we KNOW that
it's a simple for loop.  So what's the big problem?  If you don't have the
general for loop, you say

	expr1;
	while (expr2) {
		stmt;
		expr3;
	}

And now expr1, 2, and 3 are all split up.  There are cases where they
logically belong together but don't fit into the simple counter loop
model, and this is exactly what the for loop is for!

> With more complicate exit conditions, while loop fits better :
> 
>  	m = meeble;
>  	while ((m < meeble+NMEEBLE) && (m->glop==forp || m->zip==fweep)){
>           /* walk thru the array till we hit the end or
>            * find the right one */
>            ++m;
>         };
>  	if (m < meeble + NMEEBLE) {
>          /* found it */
>            printf("zeegle %2s", m->yorg);
>            m->blazzo += IGUAP;
>  	} else {
>          /* not found it */
>         }
> 

Sorry, I don't buy it.  Here's the original code, that to me
just plain looks simpler and better:

	for (m = meeble; m < meeble + NMEEBLE; m++)
		if (m->glop == forp || m->zip == fweep) {
			printf("zeegle %2s", m->yorg);
			m->blazzo += IGUAP;
			break;
		}

In *THIS* version, the for loop is immediately recognizable as a
simple for loop, and all the looping business with m is in one
place.

This really looks like another stupid religious style issue; which
one of the two versions is in an absolute sense "better" cannot be
determined.  I prefer the latter, but the former is also acceptable
to me.  But you go so far as to say that the mere *USE* of break
implies quick, hasty, poor design choices.  That's going too far.

> In some language like Pascal there is no conditional boolean operator, 
> we have to use boolean flages. With the availability of && and || in C,
> exit conditions can be coded more explicitly than with boolean flags.

Conditional and/or is another one of the great things C has.  This is
an aside, but most arguments against break/etc are that it isn't all
pretty and provable.  Well, && and || are much more of a pain to prove
right in code than Pascal-ish and/or, and assignments in expressions
wreak havoc.  But I'm trying to write a program, not a thesis.

In one of my courses, we used a very nifty language that was well-suited
to logical proofs.  The if stmt was like a lisp cond, and the easiest,
cleanest way to describe it was that it picked one of the true clauses
*at random*.  None of this annoying ordering (as in && and ||) and no
"else".  It really is a very elegant and beautiful language and you can
write proofs for programs easily, but *it's not supposed to be a real
language* (I mean, how would you implement a non-deterministic if?).
But a *real* language needs else, &&, ||, break, etc.

Can anyone suggest a recoding of the following example without using
multiple returns?  Of course it can be done, but in a clearer, simpler
way?  Remember, this is exactly the same as using loops with continue.

core(file)
char	*file;
{
	if ((fd = open(file, O_RDONLY)) < 0) {
		perror(file);
		return;
	}
	if ((n = read(fd, &u, sizeof u)) == 0) {
		puts("zero length");
		return;
	}
	if (n < sizeof u) {
		puts("too small");
		return;
	}
	if (BADMAG(u.u_exdata.ux_magic)) {
		puts("not a core dump");
		return;
	}

	/* process core dump */
	printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
	printf("$ %s\n", u.u_comm);
	/* ... etcetera */
}
-- 
						Michael Baldwin
						{at&t}!whuxl!mike

jsdy@hadron.UUCP (Joseph S. D. Yao) (11/14/85)

In article <521@busch.UUCP> dcm@busch.UUCP (Craig Miller) writes:
>	Today's C question:  how come C won't let you increment a global,
>	static or automatic array name, but will let you increment an array
>	name that's a function argument?  Film at 11.

Craig, I hope you're not serious.  You've been around this newsgroup
(I thought) since the last time I wrote >= 100 ll on this subject.

For those who weren't, the problem is that when one seems to pass
an array as an argument in C:
	char name[128];
		...
	foofunc(name);
...
foofunc(str)
  char str[];
{
one isn't really.  This is one case where C violates the Rule of
saying what you mean.  For historical reasons (see diatribe in
archives), what is passed is the address of the first element of
the array -- a pointer instead of the array!  This is one reason
(of two basic ones) that there is the confusing statement somewhere
in K&R that pointers and array addresses are treated identically.
In this case, a correct declaration of str would be:
  char *str;
but I may just want to emphasize the array-ness of it [*sigh*].
In any case,
	an array is a set of values at a fixed location.  we
	cannot change the value of that fixed location (name++)
	any more than we can change any other fixed value
	(foofunc++ or foofunc()++) or constant (1++ ?????).

	a pointer is a single pointer-size (not necessarily word)
	location in memory in which can be placed an address, such
	as the address of the beginning of that array.  The pointer
	can be changed:  in particular, it can be incremented and
	decremented (str++).

More concisely:
	a pointer name represents an l-value.
	an array name doesn't.
-- 

	Joe Yao		hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}

dimitrov@csd2.UUCP (Isaac Dimitrovsky) (11/15/85)

[]
> Can anyone suggest a recoding of the following example without using
> multiple returns?  Of course it can be done, but in a clearer, simpler
> way?  Remember, this is exactly the same as using loops with continue.
>
> core(file)
> char	*file;
> {
>	if ((fd = open(file, O_RDONLY)) < 0) {
>		perror(file);
>		return;
>	}
>	if ((n = read(fd, &u, sizeof u)) == 0) {
>		puts("zero length");
>		return;
>	}
>	if (n < sizeof u) {
>		puts("too small");
>		return;
>	}
>	if (BADMAG(u.u_exdata.ux_magic)) {
>		puts("not a core dump");
>		return;
>	}
>
>	/* process core dump */
>	printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
>	printf("$ %s\n", u.u_comm);
>	/* ... etcetera */
> }

core(file)
char	*file;
{
	if ((fd = open(file, O_RDONLY)) < 0)
		perror(file);
	else if ((n = read(fd, &u, sizeof u)) == 0)
		puts("zero length");
	else if (n < sizeof u)
		puts("too small");
	else if (BADMAG(u.u_exdata.ux_magic))
		puts("not a core dump");
	else {
		/* process core dump */
		printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
		printf("$ %s\n", u.u_comm);
		/* ... etcetera */
	}
}

Oh no!!!
Not the straightjacket again!!!
Gaaahhhhhrrgh....

Isaac Dimitrovsky
allegra!cmcl2!csd2!dimitrov   (l in cmcl2 is letter l not number 1)
251 Mercer Street, New York NY 10012     (212) 674-8652

... Hernandez steps in to face ... Orl ... HERchiiiser ... and it's a liiine
driive, deeeeep to the gap in left center ...	- Bob Murphy, Voice of the Mets

cjl@iuvax.UUCP (11/15/85)

> ..................... Well, && and || are much more of a pain to prove
> right in code than Pascal-is and/or, and assignments in expressions
> wreak havoc.  But I'm trying to write a program, not a thesis.

  A block of codes is considered structured and simple if it has a simple
precondition and postcondition formally or informally. So I agree that break
and continue could form a structured loop if they are appropriately used.
                                                          
  What I am worrying about is how the postcondition of a multi-exit loop
can be EASILY recovered (I assume it is not normally documented). Semantically
we need a loop invariant (normally implied) before the exit point plus the
exit condition to assert the post-loop condition.   Most multi-exit loops 
I saw can be syntactically written as a single exit loop with 
conditional boolean expression (or a multi-exit loop with nested exitif
statements like 'exit when' in Ada). To me syntactic grouping of 
all exit conditions is important because it enforces ONE clear synchronization 
point for the (implied) loop invariant. Whenever loop exit points are
scattering around, the loop invariant is harder to find. Or we may even
have to assert many loop invariants for these exit points. 

  The use of conditional boolean exit conditions also reminds me to
eliminates them after terminating the loop because they are hard to
carry on in pure (non-ordered) boolean reasoning(e.g. not commutative). 
To eliminate the conditional boolean expression, we recover it to
their original if statement form. For example :

  i = 0;
  /* Loop Invariant : (i <= HIGH+1) and X is not found in array[0..i-1] */
  while ( (i <= HIGH) && (array[i] <> X) {
    i++
  }       

  if (i = HIGH+1) { /* X is not found in array[0..HIGH] */
    .... not found .....
  } else { /* (i < HIGH+1) and X is not found in [0..i-1] & (array[i]=X) */
    ... found .........
  }
 
In contrast :
 
  for (i:=0; ++i; i>HIGH) 
    if (array[i] = X) {
      break 
    } 

is less clear in the sense that the position of loop invariant is not
obvious. Also it is less clear how the exit conditions should be treated 
after loop termination.
  In general it is too complex to reason, thus to design, a loop with 
general random exit points. Most multi-exit loops we face today are in
a restricted form. So why don't we impose a restriction on the use of
multi-exit loops ? The introduction of boolean expression and single
exit loop is one approach to impose a style on disciplined uses of 
multi-exit loops.  One can find other equivalent forms as well.

    Above is an example of using pure computer science theory
to judge the complexity of multi-exit loops. I'll to glad to find
different situations where reasoning multi-exit loops is easy.
        
> Can anyone suggest a recoding of the following example without using
> multiple returns?  Of course it can be done, but in a clearer, simpler
> way?  Remember, this is exactly the same as using loops with continue.

  It all depends on what postcondition you want to assert after procedure
termination. For this example, the return implies "abort". No serious
assertion  on the postcondition is considered as necessary. 
  But for other procedures it will be less clear what the post condition
is if multi-return is used. In the procedure environment, this problem
could be less serious because normally the pre- and postconditions are 
explicitly documented. We don't rely heavily on the multi-return
condition to draw the postcondition. If we ever think and document
the postcondition of our multi-exit loop in a similar serious way like
we write procedures, the problem will be less serious.


C.J.Lo
ARPA : cjl@Indiana@CSNet-Relay
UUCP : ...!iuvax!cjl

gwyn@BRL.ARPA (VLD/VMB) (11/15/85)

Just a note on the nondeterministic "if" (a.k.a. Dijkstra's
guarded command construct):  The selected case need NOT be
picked "at random".  The choice need not even be "fair"
in a statistical sense, for most uses of this construct.
The implementation can e.g. always pick the first true
case; however, the programmer is not allowed to make use
of this implementation detail.  The claim that such a
language differs from what is needed in a "real language"
needs some substantiation.  Since this newsgroup is for
C, however, further dialogue on this should be conducted
elsewhere.  (By the way, Dijkstra's "A Discipline of
Programming" is a real classic.  A funny thing is, he
claims that his discipline results in correct programs,
but there are at least three known bugs in his examples.)

David Gries wrote a textbook on the same subject which
is probably more accessible to most programmers.  I think
it was entitled "The Science of Computer Programming".  I
hope more people study this subject, so I will have fewer
bugs to deal with in the future.

jak@adelie.UUCP (Jeff Kresch) (11/15/85)

> Can anyone suggest a recoding of the following example without using
> multiple returns?  Of course it can be done, but in a clearer, simpler
> way?  Remember, this is exactly the same as using loops with continue.
> 
> core(file)
> char	*file;
> {
> 	if ((fd = open(file, O_RDONLY)) < 0) {
> 		perror(file);
> 		return;
> 	}
> 	if ((n = read(fd, &u, sizeof u)) == 0) {
> 		puts("zero length");
> 		return;
> 	}
> 	if (n < sizeof u) {
> 		puts("too small");
> 		return;
> 	}
> 	if (BADMAG(u.u_exdata.ux_magic)) {
> 		puts("not a core dump");
> 		return;
> 	}
> 
> 	/* process core dump */
> 	printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
> 	printf("$ %s\n", u.u_comm);
> 	/* ... etcetera */
> }

How about:

 core(file)
 char	*file;
 {
        bool error = TRUE;

 	if ((fd = open(file, O_RDONLY)) < 0)
 	        perror(file);
        else if ((n = read(fd, &u, sizeof u)) == 0)
 		puts("zero length");
        else if (n < sizeof u)
 		puts("too small");
        else if (BADMAG(u.u_exdata.ux_magic))
 		puts("not a core dump");
        else
                error = FALSE;
        if (error)
                return;
 
 	/* process core dump */
 	printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
 	printf("$ %s\n", u.u_comm);
 	/* ... etcetera */

This version is a good deal smaller, which, in this case, makes the
program easier to read.

But I agree with the basic premise that multiple breaks, continues, and
returns are not the same as, and as bad as, gotos.  The difference is
that the former work within the context of a structure.  A return always
returns to the same place.  Breaks and continues function the same way
within loops.  Gotos can be used arbitrarily, and that is the danger.

                                                They call me
                                                JAK

"I just like to see may name in print."

jsdy@hadron.UUCP (Joseph S. D. Yao) (11/16/85)

In article <806@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes:
>>   In addition, I recommend the use of for loop be restricted to 
>> its original meaning in natural language, i.e. as a loop with simple 
>> counter.
[Note ">>": second attribution was missing.]

#define dofor(var,init,lim,incr)	\
		for (var = init; var < lim; lim += incr)
#define dofor1(var,init,lim)	for(var = init; var < lim; var++)

etc.  Just don't present me with any code using these macros.  Now
can we discuss something real?
-- 

	Joe Yao		hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}

gemini@homxb.UUCP (Rick Richardson) (11/17/85)

>   What I am worrying about is how the postcondition of a multi-exit loop
> can be EASILY recovered (I assume it is not normally documented). 
> ... MORE HYPE ... Then these examples:
  i = 0;
  /* Loop Invariant : (i <= HIGH+1) and X is not found in array[0..i-1] */
  while ( (i <= HIGH) && (array[i] <> X) {
    i++
  }       

  if (i = HIGH+1) { /* X is not found in array[0..HIGH] */
    .... not found .....
  } else { /* (i < HIGH+1) and X is not found in [0..i-1] & (array[i]=X) */
    ... found .........
  }
 
In contrast :
 
  for (i:=0; ++i; i>HIGH) 
    if (array[i] = X) {
      break 
    } 

> is less clear in the sense that the position of loop invariant is not
> obvious. Also it is less clear how the exit conditions should be treated 
> after loop termination.

If this second example is less clear than the first, then I must be getting
too old to grok whatever they are teaching in school these days.  To me,
the important part is whether the next guy (or me, 6 months later) can
understand the code.  The second example is much easier to understand than
the first.  Maybe it is harder to prove (though I'll admit I don't know),
but:
	1) So what.  I *never* run proveit(1) on my programs.
		    (anybody got a copy).
	2) Let me program what is understandable to humans.
	   If the program provers can't handle my code, that is their
	   deficiency, not mine. 
Rick Richardson
PC Research, Inc.
P.S. Your examples aren't even close to compilable 'C'.  Perhaps if you
had written some 'C' code, you might give up your religion for the *real*
world. I did.

alexis@reed.UUCP (Alexis Dimitriadis) (11/18/85)

References:


  Another reason to prefer `continue' (especially) to an if-else
construction is that a continue makes it _obvious_ that nothing more
will be done to this instance of the loop.  With an if-else
construction, it is necessary to make sure there is no code between the
end of the else block and the end of the loop or function block.  This
is not too bad if Craig's suggestion to keep things small is followed,
but I still feel that a continue _adds_ clarity and makes code easier
and safer to modify.

Alexis Dimitriadis
(still at large)
-- 
_______________________________________________
  As soon as I get a full time job, the opinions expressed above
will attach themselves to my employer, who will never be rid of
them again.
				alexis @ reed
    {decvax,ihnp4,ucbcad,uw-beaver}!tektronix!reed.UUCP

mikes@3comvax.UUCP (Mike Shannon) (11/18/85)

Michael Baldwin:
> Can anyone suggest a recoding of the following example without using
> multiple returns?  Of course it can be done, but in a clearer, simpler
> way?  Remember, this is exactly the same as using loops with continue.
> 
> core(file)
> char	*file;
> {
> 	if ((fd = open(file, O_RDONLY)) < 0) {
> 		perror(file);
> 		return;
> 	}
> 	if ((n = read(fd, &u, sizeof u)) == 0) {
> 		puts("zero length");
> 		return;
> 	}
> 	if (n < sizeof u) {
> 		puts("too small");
> 		return;
> 	}
> 	if (BADMAG(u.u_exdata.ux_magic)) {
> 		puts("not a core dump");
> 		return;
> 	}
> 
> 	/* process core dump */
> 	printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
> 	printf("$ %s\n", u.u_comm);
> 	/* ... etcetera */
> }
--------
OK, here goes:
--------
core(file)
char	*file;
{
	if ((fd = open(file, O_RDONLY)) < 0) {
		perror(file);
	}
	else if ((n = read(fd, &u, sizeof u)) == 0) {
		puts("zero length");
	}
	else if (n < sizeof u) {
		puts("too small");
	}
	else if (BADMAG(u.u_exdata.ux_magic)) {
		puts("not a core dump");
	}
	else { /* all is OK */
		/* process core dump */
		printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start));
		printf("$ %s\n", u.u_comm);
		/* ... etcetera */
	}
}
-----
	This case is *exactly* the situation where I don't like to see
multiple returns.  If there's one thing I hate, it's seeing a bunch of
branch statements.  Then I have to scratch my head and think "Ok, where
can I get to as I scan down this code?". Really.  When I see goto's,
multiple return's, continue's, etc, immediately, in my head, I think,
"Oh Crap! Now I really have to pay attention!".
	I really think my second way is better.  If I had to debug this
code, I would read it and it's immediately obvious what's going on.  This
'multiple error filter' use of the if.... else if..... else  seems
so intiutive to me that I can trivially scan it and instantly know
what's going on.  Of course, it's *always* important to pay attention
when reading/debugging someone else's code. (or even your own! :-)
	But why make me expend any more mental calories than I need to?
	Along the same line, if you have a situation like this:
	if(a) {
		something
	}
	if(b) {
		something else
	}
and (a) and (b) are mutally exclusive conditions, then please do everyone a
favor, and do it this way:
	if(a) {
		something
	}
	else if (b) { /* notice the ELSE */
		something else
	}

that way, when someone comes along later to read your programs, they
won't have to worry about "what if both are supposed to happen?"
-- 
			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes

oleg@birtch.UUCP (Oleg Kiselev) (11/19/85)

In article <283@3comvax.UUCP> mikes@3comvax.UUCP (Mike Shannon) writes:
>Michael Baldwin:
>> Can anyone suggest a recoding of the following example without using
>> multiple returns?  Of course it can be done, but in a clearer, simpler
                      ^^^^^^^^^                          ^^^^^^^^^^^^^^^^!!!
>> way?  Remember, this is exactly the same as using loops with continue.
>> 
>> core(file)
>> char	*file;
>> {
>>[CODE WITH MULTIPLE RETURNS]
>> }
>--------
>OK, here goes:
>--------
>core(file)
>char	*file;
>{
>[SIMILAR CODE USING IF...THEN...ELSE IF....]
>}
>
>-----
>	This case is *exactly* the situation where I don't like to see
>multiple returns.  If there's one thing I hate, it's seeing a bunch of
>branch statements.  Then I have to scratch my head and think "Ok, where
>can I get to as I scan down this code?". Really.

"return" is not much of a "branch" statement. It is fairly unambiguous. What
it say to ME is "this is it! No need to look farther. Return to caller"

>	I really think my second way is better.  If I had to debug this
>code, I would read it and it's immediately obvious what's going on.  This
>'multiple error filter' use of the if.... else if..... else  seems
>so intiutive to me that I can trivially scan it and instantly know
>what's going on.  

Without multiple returns you have to scan the entire routine and check
every "if" and "switch", make sure there is no problems with improperly
balanced "if...then...else if"s, etc. "return" just tells you very plainly :
"If this is the case, take action, and RETURN". That's it! no need do follow
a lengthy and convoluted ( and possibly buggy) chain of "if"s, no need to 
scatter your attention on the entire page of code, trying to find out what 
actions OTHER than the ones specified in the original "if" are taken: what flags
are set, or reset; what other routines might be called, etc. With "return" there
is no such problem - you LEAVE the routine! 

>			Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes

Your example might be scholasticaly more proper (no goto's, multiple returns, 
breaks, continues) but it is NOT *clearer* or *simpler*!

In fact, why do you think so many NON-standard Pascal compilers have "LEAVE"
instruction to break out of a loop or return early from  a procedure? Because
Pascal does not allow multiple "return"s and people seem to need them badly
enough to put them into the language!
-- 
Disclamer: My employers go to church every Sunday, listen to Country music,
and donate money to GOP. I am just a deviant.
+-------------------------------+ Don't bother, I'll find the door!
| "VIOLATORS WILL BE TOAD!"	|                       Oleg Kiselev. 
|		Dungeon Police	|...!{trwrb|scgvaxd}!felix!birtch!oleg
--------------------------------+...!{ihnp4|randvax}!ucla-cs!uclapic!oac6!oleg

franka@mmintl.UUCP (Frank Adams) (12/03/85)

[Not food]

The real case for imbedded returns is in the following sort of construct:

if (a) {
   stuff;
   if (b) {
      more stuff;
      if (c) {
         return;    /* oops! */
      }
      still more stuff;
   }
   even more stuff;
}

One can deal with "still more stuff" with an 'else if' clause, but "even
more stuff" is harder.  Basically, one needs a switch:

bool ok;
if (a) {
   ok = TRUE;
   stuff;
   if (b) {
      more stuff;
      if (c) {
         ok = FALSE;    /* oops! */
      }
      else {
         still more stuff;
      }
   }
   if (ok) {
      even more stuff;
   }
}

Personally, I think this is distincly less readable than the first version.
Furthermore, as the complexity of the program flow increases, the amount of
overhead to keep track of switches increases; the cost of a return or break
does not.

There does come a point where it is better to split the parts into separate
modules, but I think that point is at about twice the complexity of my
example.  This leaves lots of intermediate programs where return or break
is a clear win.  (Raising exceptions is better, but isn't available in c.)

Frank Adams                           ihpn4!philabs!pwa-b!mmintl!franka
Multimate International    52 Oakland Ave North    E. Hartford, CT 06108

peter@baylor.UUCP (Peter da Silva) (01/18/86)

> > Too many times, programmers are seduced by 
> > the use of break for a quick solution without spending time to structure 
> > their program more. That is the same lesson we learned from advocating 
> > goto-less programs.
> 
> Oh, baloney!  I happen to think that my use of continue/return/break in
> my code is perfectly well thought out and structured.  If you think it's
> not just *BECAUSE* it uses continue/return/break, you're being too hasty.
> BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS.  IF THEY WERE,
> THEY'D ALL BE CALLED "GOTO".

More fuel. I just (Wednesday) went through & put gotos into the main
program of all my little utilities. Why? Because "break" doesn't work
the same way as continue when you're inside a switch. I got sick and
tired of messing with flags & had the choice of either using gotos or
turning my switches (another structured goto, by the way) into if-then-else
chains. It would be nice if 'C' didn't overload keywords this way. Perhaps
'exit' could have been used here.

BTW: I wholeheartedly agree with Michael's comments.
-- 
-- Peter da Silva
-- UUCP: ...!shell!{baylor,graffiti}!peter; MCI: PDASILVA; CIS: 70216,1076

ron@brl-smoke.ARPA (Ron Natalie <ron>) (01/20/86)

> > BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS.  IF THEY WERE,
> > THEY'D ALL BE CALLED "GOTO".
> 
> More fuel. I just (Wednesday) went through & put gotos into the main
> program of all my little utilities. Why? Because "break" doesn't work
> the same way as continue when you're inside a switch.

Wonderful, break never works the same way as continue, or else they'd
be called the same thing.

It is possible to do terribly unstructured things with GOTO, BREAK,
RETURN, and CONTINUE, but one should not attempt to enforce structured
programming at the language level because it won't work.  You can
write structured code in any language, and real programmers can code
Fortran programs in any language as well.

-Ron