Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

for ... else blocks

101 views
Skip to first unread message

Rick C. Hodgin

unread,
Apr 16, 2019, 2:56:21 PM4/16/19
to
I'm curious what people's thought are on this idea. It allows the initial
test on the for {..} block to enter the block, or fall-through to the else
clause. Subsequent tests on the for {..} block that fail simply exit out
past the last }.

for (init; test; incr)
{
// for code here

} else {
// else code here
}

It's the equivalent of:

init;
if (test)
{
goto skip_first_iteration;
for ( ; test; incr)
{
skip_first_iteration:
// for code here
}

} else {
// else code here
}

Seems a natural progression. One I could use quite often due to the
need to test the conditions outside of the for {..} block today before
entering it with then-known valid data. If the test for a valid state
could be incorporated into the generic iterative test, and the else
clause added, it would be more natural methinks.

--
Rick C. Hodgin

Rick C. Hodgin

unread,
Apr 16, 2019, 3:05:31 PM4/16/19
to
On 4/16/2019 2:57 PM, Rick C. Hodgin wrote:
> I'm curious what people's thought are on this idea.  It allows the initial
> test on the for {..} block to enter the block, or fall-through to the else
> clause.  Subsequent tests on the for {..} block that fail simply exit out
> past the last }.
>
>     for (init; test; incr)
>     {
>         // for code here
>
>     } else {
>         // else code here
>     }
>
> It's the equivalent of:
>
>     init;
>     if (test)
>     {
>         goto skip_first_iteration;
>         for ( ; test; incr)
>         {
> skip_first_iteration:

Should be "skip_first_test:".

>             // for code here
>         }
>
>     } else {
>         // else code here
>     }
>
> Seems a natural progression.  One I could use quite often due to the
> need to test the conditions outside of the for {..} block today before
> entering it with then-known valid data.  If the test for a valid state
> could be incorporated into the generic iterative test, and the else
> clause added, it would be more natural methinks.

Another variant could be:

for_if (test1; init; test2; incr)
{
} else {
}

test1 is the if (test1) test, and if it fails, goes to the else. The
test2 is the iterative test which processes like normal. This one could
be used when test1 and test2 are different, or are not overlapping.

Of course "else if" blocks could also be used, and I think also a new
concept of "else for" block could also be used:

for (init1; test1; incr1)
{
// First for code here

} else for (init2; test2; incr2) {
// Second for code here

} else for (init3; test3; incr3) {
// Third for code here

} else {
// Fallthrough code
}

Same for:

for_if (test1_1; init1; test1_2; incr1)
{
// First for_if code here

} else for_if (test1_1; init1; test1_2; incr1) {
// Second for_if code here

} else for_if (test1_1; init1; test1_2; incr1) {
// Third for_if code here

} else {
// Fallthrough code
}

And, of course, for {..} and for_if {..} blocks could be intermixed.

--
Rick C. Hodgin

Bart

unread,
Apr 16, 2019, 3:27:22 PM4/16/19
to
On 16/04/2019 19:57, Rick C. Hodgin wrote:
> I'm curious what people's thought are on this idea.  It allows the initial
> test on the for {..} block to enter the block, or fall-through to the else
> clause.  Subsequent tests on the for {..} block that fail simply exit out
> past the last }.
>
>     for (init; test; incr)
>     {
>         // for code here
>
>     } else {
>         // else code here
>     }
>
> It's the equivalent of:
>
>     init;
>     if (test)
>     {
>         goto skip_first_iteration;
>         for ( ; test; incr)
>         {
> skip_first_iteration:
>             // for code here
>         }
>
>     } else {
>         // else code here
>     }
>
> Seems a natural progression.

Some languages have for-else, but that's not how it's used.

The use-cases in those other languages are more useful, and easier to
understand, than what you have. There, a for-else loop like this:

for (A; B; C) {
D;
if (X) break;
} else {E;}

is equivalent to:

for (A; B; C) {
D;
if (X) goto skipelse;
}
E;
skipelse:

The else-clause is executed on normal loop terminated. But not when
terminated by 'break'.

Note that when B starts off as false, then this is equivalent to your
version: it will just execute E. But when B starts as true, then some
iterations are executed before E, unlike yours that will do nothing
special when the loop ends normally after 1+ iterations.

(Note also that 'for(){} else' has some syntactic issues in C, as the
'else' could taken to belong to the last if-block at the same block
level. A rule change to make it belong to the last 'if' or 'for' would
break existing code.)

Christian Gollwitzer

unread,
Apr 16, 2019, 3:35:01 PM4/16/19
to
Am 16.04.19 um 20:57 schrieb Rick C. Hodgin:
> I'm curious what people's thought are on this idea.  It allows the initial
> test on the for {..} block to enter the block, or fall-through to the else
> clause.  Subsequent tests on the for {..} block that fail simply exit out
> past the last }.

Such a construct exists in the Python language:

http://book.pythontips.com/en/latest/for_-_else.html

(and also with while loops). It is occasionally useful, but more often
then actually used, it is asked by confused Python learners what exactly
this means, under which conditions the else block will run, and how it
is different from just putting something after a regular for block.

Christian

Bart

unread,
Apr 16, 2019, 3:36:10 PM4/16/19
to
On 16/04/2019 20:06, Rick C. Hodgin wrote:

> Another variant could be:
>
>     for_if (test1; init; test2; incr)
>     {
>     } else {
>     }
>
> test1 is the if (test1) test, and if it fails, goes to the else.

I thought you wanted 'init' to be executed first? Otherwise there seems
little point to this, as it is exactly equivalent to:

if (test1) for (init; test2; incr)
{
} else {
}

which can be done right now. And without encouraging people to abuse
for-headers even more than they already do. (I know how compulsive it is
to cram as much in there as possible.)

  The
> test2 is the iterative test which processes like normal.  This one could
> be used when test1 and test2 are different, or are not overlapping.
>
> Of course "else if" blocks could also be used, and I think also a new
> concept of "else for" block could also be used:
>
>     for (init1; test1; incr1)
>     {
>         // First for code here
>
>     } else for (init2; test2; incr2) {
>         // Second for code here
>
>     } else for (init3; test3; incr3) {
>         // Third for code here
>
>     } else {
>         // Fallthrough code
>     }


So, if test1 is false to start with it will do try the second for; and
if test2 is false to start it will try the third for?

Do you have any actual use-cases for this?

Rick C. Hodgin

unread,
Apr 16, 2019, 4:53:00 PM4/16/19
to
Good to hear. Thank you, Christian.

--
Rick C. Hodgin

Rick C. Hodgin

unread,
Apr 16, 2019, 5:01:17 PM4/16/19
to
On 4/16/2019 3:35 PM, Bart wrote:
> On 16/04/2019 20:06, Rick C. Hodgin wrote:
>
>> Another variant could be:
>>
>>      for_if (test1; init; test2; incr)
>>      {
>>      } else {
>>      }
>>
>> test1 is the if (test1) test, and if it fails, goes to the else.
>
> I thought you wanted 'init' to be executed first? Otherwise there seems
> little point to this, as it is exactly equivalent to:
>
>      if (test1) for (init; test2; incr)
>      {
>      } else {
>      }

I think you can already do your syntax in C/C++.

if (test1)
for (...)
{
// for code
}

else {
// else code
}

> which can be done right now. And without encouraging people to abuse
> for-headers even more than they already do. (I know how compulsive it is to
> cram as much in there as possible.)

The purpose of the for_if would be to test the outer condition in a way
that's part of the for block, allowing it to then fall through to an
else clause which could be another for_if or for block, with the second
test being one which falls out of the entire loop if it is entered one
time.

>   The
>> test2 is the iterative test which processes like normal.  This one could
>> be used when test1 and test2 are different, or are not overlapping.
>>
>> Of course "else if" blocks could also be used, and I think also a new
>> concept of "else for" block could also be used:
>>
>>      for (init1; test1; incr1)
>>      {
>>          // First for code here
>>
>>      } else for (init2; test2; incr2) {
>>          // Second for code here
>>
>>      } else for (init3; test3; incr3) {
>>          // Third for code here
>>
>>      } else {
>>          // Fallthrough code
>>      }
>
> So, if test1 is false to start with it will do try the second for; and if
> test2 is false to start it will try the third for?

It would be used in cases where part of the test1 would be overlapping.

if (x)
{
for (init; x && x->member != whatever; iter)
{
// Process

// Move through the pointer chain
x = x->next;
}
}

In this case, you only want to execute the init code if x is valid to
start out with.

> Do you have any actual use-cases for this?

I came across a need for it today in my code, which is why / how it
occurred to me.

--
Rick C. Hodgin

David Brown

unread,
Apr 16, 2019, 5:32:23 PM4/16/19
to
That is new to me. I can see it being useful in some cases (like the
examples shown in that link), though I am not convinced it is very
clear. I think Python experts would be confused by it, never mind
Python learners.

David Brown

unread,
Apr 16, 2019, 5:36:30 PM4/16/19
to
I think you'll need to show some concrete examples in order to show the
point.

At the moment, I'm left thinking that you are constructing complicated
syntax for the programmer to write things that a compiler can handle
already using simple C syntax and some optimisations.

Alf P. Steinbach

unread,
Apr 16, 2019, 6:45:30 PM4/16/19
to
I don't see Rick's postings, but I imagine what he wants is a construct
for this (note: I think it indicates the lambda should be a named func):

const bool early_loop_exit = invoke( [&]() -> bool
{
for( INIT; COND; UPDATE )
{
if( SOMETHING ) { return true; }
}
return false;
} );

if( not early_loop_exit )
{
// The logical "else" part.
}

It can also be expressed with all code inline in current func with
exceptions. The `return false` then corresponds to throwing of an
exception. I believe that's how the construct works in CPython.

The exception based logic would look more clean but potentially carries
some execution overhead for the "else" case.


Cheers!,

- Alf

Ben Bacarisse

unread,
Apr 16, 2019, 8:06:42 PM4/16/19
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> I don't see Rick's postings, but I imagine what he wants is a
> construct for this (note: I think it indicates the lambda should be a
> named func):
>
> const bool early_loop_exit = invoke( [&]() -> bool
> {
> for( INIT; COND; UPDATE )
> {
> if( SOMETHING ) { return true; }
> }
> return false;
> } );
>
> if( not early_loop_exit )
> {
> // The logical "else" part.
> }

That looks like the Python meaning, not the one RCH was suggesting. I
think RCH's intent can be written as:

bool loop_body_executed = false;
for (INIT; COND; UPDATE, loop_body_executed = true) {
// ...
}
if (!loop_body_executed) {
// The logical "else" part.
}

--
Ben.

Alf P. Steinbach

unread,
Apr 16, 2019, 10:45:26 PM4/16/19
to
Thanks. That seems to indicate that there are at least two about equally
convincing but exactly opposite notions of what `else` should mean if it
were introduced as an extension to `for`...

---

Anyway I find your example interesting because it's easier to read, and
therefore grok, than my example that, more ideally, constrains the scope
for modification of the variable.

And I think that's due to two reasons:

• That your example is shorter, mainly because of formatting.
• That C++, from its C heritage, supports the changing-variables style
of coding, and does not so much support expression based coding.

In some other contexts the shorter formatting tends to produce what in
my eyes is a gray wall of compact code, so I'm wary of adopting it. It's
nice for books and articles, though, where vertical space is a limited
resource. But on the third and gripping hand I may be too old to change
formatting preferences again...

---

Going back even further historically, C's parent language BCPL had a
construct that one can think of as “arithmetic blocks”, like a curly
braces compound statement that could return a value.

And expressing your example with an imaginary such construct it's not
significantly more to read, and then properly constrains the scope where
the code can change or influence the value of the boolean:

const bool loop_body_executed = BCPL_BLOCK {
for (INIT; COND; UPDATE) {
if( SOMETHING ) { return true; }
}
return false;
};
if( not loop_body_executed ) {
// The logical "else" part.
}


---

As it happens it's easy to define a macro like BCPL_BLOCK:

#define BCPL_BLOCK ~[&]()

... where the `~` operator is defined as e.g.

namespace lambda_support
{
using std::declval;

template< class Func
, class Enabled_ = decltype( declval<Func>()() )
>
inline auto operator~( Func const& f )
-> decltype(( f() ))
{ return f(); }
} // namespace lambda_support

One just needs a `using` statement for it. Hence the need for SFINAE to
ensure that this definition only kicks in, that it's only found, when
`~` is applied to something callable without arguments. It should
probably be even more constrained than what I managed above.

So, that notation is not hypothetical. It's also easy to provide
bindings for the lambda, e.g. using the `%` operator. It would be (or
was when I tried it out, some years ago, then named `$invoked_with`
instead of BCPL_BLOCK) a rather ugly hack that didn't entirely prevent
incorrect use, but if one could get language and/or standard library
support then it could be safe, because the standard library is allowed
to use magic. I'd prefer a core language prefix invocation operator.


Cheers!,

- Alf

David Brown

unread,
Apr 17, 2019, 3:32:41 AM4/17/19
to
On 17/04/2019 04:45, Alf P. Steinbach wrote:
> On 17.04.2019 02:06, Ben Bacarisse wrote:
>> "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
>>
>>> I don't see Rick's postings, but I imagine what he wants is a
>>> construct for this (note: I think it indicates the lambda should be a
>>> named func):
>>>
>>>      const bool early_loop_exit = invoke( [&]() -> bool
>>>      {
>>>          for( INIT; COND; UPDATE )
>>>          {
>>>              if( SOMETHING ) { return true; }
>>>          }
>>>          return false;
>>>      } );
>>>
>>>      if( not early_loop_exit )
>>>      {
>>>          // The logical "else" part.
>>>      }
>>
>> That looks like the Python meaning, not the one RCH was suggesting.  I
>> think RCH's intent can be written as:
>>
>>    bool loop_body_executed = false;
>>    for (INIT; COND; UPDATE, loop_body_executed = true) {
>>       // ...
>>    }
>>    if (!loop_body_executed) {
>>      // The logical "else" part.
>>    }
>>
>
> Thanks. That seems to indicate that there are at least two about equally
> convincing but exactly opposite notions of what `else` should mean if it
> were introduced as an extension to `for`...
>

And that would indicate that neither is a good idea, and "for else"
should be avoided. Both cases can be easily and clearly handled by a
bool flag, which a good compiler can often eliminate entirely (and it's
a cheap solution even if the compiler can't remove it).

>   ---
>
> Anyway I find your example interesting because it's easier to read, and
> therefore grok, than my example that, more ideally, constrains the scope
> for modification of the variable.
>
> And I think that's due to two reasons:
>
> • That your example is shorter, mainly because of formatting.
> • That C++, from its C heritage, supports the changing-variables style
> of coding, and does not so much support expression based coding.
>

I think the principle of avoiding changing variables is good, but it can
be taken too far. Many factors contribute to code clarity - obsessing
over any one of them is likely to give you worse (i.e., less legible)
code in the end.
You do like to come up with some convoluted code, don't you? I found
that one interesting, however. Your next challenge is to use concepts
to remove the "Enabled" bit.

Bart

unread,
Apr 17, 2019, 6:38:18 AM4/17/19
to
On 17/04/2019 08:32, David Brown wrote:

>> Thanks. That seems to indicate that there are at least two about equally
>> convincing but exactly opposite notions of what `else` should mean if it
>> were introduced as an extension to `for`...

Its use in Python is not hard to understand.

Its use as described in the OP is harder (RCH's ideas for features
always are).

> And that would indicate that neither is a good idea, and "for else"
> should be avoided. Both cases can be easily and clearly handled by a
> bool flag, which a good compiler can often eliminate entirely (and it's
> a cheap solution even if the compiler can't remove it).

The idea is to avoid the extra logic. Here's a typical use in one of my
languages (real example):

for sw to optionnames.len do
if eqstring(name,optionnames[sw]) then
do_option(sw,value)
exit
fi
else
println "Unknown option:",name
stop 1
od

This for a language at the level of C (I'm posting from that group). A
higher level might just say 'sw in optionnames', but since many won't
have eliminated for-loops completely, there is likely still to be a use
for for-else.

The alternative as you say is to use a flag:

optionno:=0
for sw ...
...
optionno:=sw
...
od
if optionno then
do_option ...
else
println ...
fi

I must have written a thousand such loops; the for-else feature provides
a welcome respite, as well as a self-contained solution in one statement.

The flag version introduces a new variable that demands an extra, wider
scope, something at odds with your preference for narrower block scopes.

David Brown

unread,
Apr 17, 2019, 8:05:48 AM4/17/19
to
On 17/04/2019 12:38, Bart wrote:
> On 17/04/2019 08:32, David Brown wrote:
>
>>> Thanks. That seems to indicate that there are at least two about equally
>>> convincing but exactly opposite notions of what `else` should mean if it
>>> were introduced as an extension to `for`...
>
> Its use in Python is not hard to understand.

That is debatable. But I agree it is easier (and more useful) than
Rick's suggestion.

>
> Its use as described in the OP is harder (RCH's ideas for features
> always are).
>
>> And that would indicate that neither is a good idea, and "for else"
>> should be avoided.  Both cases can be easily and clearly handled by a
>> bool flag, which a good compiler can often eliminate entirely (and it's
>> a cheap solution even if the compiler can't remove it).
>
> The idea is to avoid the extra logic. Here's a typical use in one of my
> languages (real example):
>
>     for sw to optionnames.len do
>         if eqstring(name,optionnames[sw]) then
>             do_option(sw,value)
>             exit
>         fi
>     else
>         println "Unknown option:",name
>         stop 1
>     od
>

bool found = false;
int sw;
for (sw = 0; sw < len; sw++) {
if (strcmp(name, options[sw]) == 0) {
found = true;
break;
}
}
if (found) {
do_option(sw, value);
} else {
printf("Unknown option: %s\r\n", name);
stop(1);
}

The "for else" construct is not necessary, and the bool flag solution
will be just as efficient. Structuring the code as "try to find the
match, then handle the result" is arguably neater (and by "arguably", I
don't mean it is /definitely/ neater). It also makes it easier to
refactor the code if you have a different way of searching, perhaps as a
separate function.


> This for a language at the level of C (I'm posting from that group). A
> higher level might just say 'sw in optionnames', but since many won't
> have eliminated for-loops completely, there is likely still to be a use
> for for-else.
>
> The alternative as you say is to use a flag:
>
>     optionno:=0
>     for sw ...
>         ...
>         optionno:=sw
>         ...
>     od
>     if optionno then
>         do_option ...
>     else
>         println ...
>     fi
>
> I must have written a thousand such loops; the for-else feature provides
> a welcome respite, as well as a self-contained solution in one statement.

A smaller and more compact solution could be nice - but that would, as
you say, require a higher level language.

>
> The flag version introduces a new variable that demands an extra, wider
> scope, something at odds with your preference for narrower block scopes.

I have a preference for narrower scopes, all else being equal. But if
the data is needed over a wider range, then a wider scope is the answer.

Bart

unread,
Apr 17, 2019, 8:57:13 AM4/17/19
to
On 17/04/2019 13:05, David Brown wrote:
> On 17/04/2019 12:38, Bart wrote:
>> On 17/04/2019 08:32, David Brown wrote:
>>
>>>> Thanks. That seems to indicate that there are at least two about equally
>>>> convincing but exactly opposite notions of what `else` should mean if it
>>>> were introduced as an extension to `for`...
>>
>> Its use in Python is not hard to understand.
>
> That is debatable.

The controversy is with the use of the 'else' keyword. The idea of
having code executed only on normal loop termination, not so much.

> if (found) {
> do_option(sw, value);
> } else {
> printf("Unknown option: %s\r\n", name);
> stop(1);
> }
>
> The "for else" construct is not necessary, and the bool flag solution
> will be just as efficient.

So why not:

if (found) {
do_option(sw, value);
}
if (!found) {
printf("Unknown option: %s\r\n", name);
exit(1);
}

We don't really need 'if else' either. (I'm sure I didn't have that in
Fortran years ago.)


> A smaller and more compact solution could be nice - but that would, as
> you say, require a higher level language.

By higher level I mean something like:

do_option(name in optionnames, value)

'in' would return 1..len or 0, so do_option would have to deal with a
option code of 0, to keep it simple.

Higher level code tends not to have so many explicit loops, but so long
as for-loops /are/ still used, then an 'else' clause containing
dedicated code for a normal termination rather than a break will still
be useful.

David Brown

unread,
Apr 17, 2019, 9:36:37 AM4/17/19
to
On 17/04/2019 14:57, Bart wrote:
> On 17/04/2019 13:05, David Brown wrote:
>> On 17/04/2019 12:38, Bart wrote:
>>> On 17/04/2019 08:32, David Brown wrote:
>>>
>>>>> Thanks. That seems to indicate that there are at least two about
>>>>> equally
>>>>> convincing but exactly opposite notions of what `else` should mean
>>>>> if it
>>>>> were introduced as an extension to `for`...
>>>
>>> Its use in Python is not hard to understand.
>>
>> That is debatable.
>
> The controversy is with the use of the 'else' keyword. The idea of
> having code executed only on normal loop termination, not so much.

It is the use of the "else" keyword that you said was not hard to
understand in Python, and I said it was debatable how hard that is. The
poster that first mentioned said that the "else" keyboard in Python
"for" loops is more often a subject of confusion than a useful coding
technique.

Clearly I don't disagree that sometimes it is useful to execute
different code when a loop is terminated early.

>
>>     if (found) {
>>         do_option(sw, value);
>>     } else {
>>         printf("Unknown option: %s\r\n", name);
>>         stop(1);
>>     }
>>
>> The "for else" construct is not necessary, and the bool flag solution
>> will be just as efficient.
>
> So why not:
>
>      if (found) {
>          do_option(sw, value);
>      }
>         if (!found) {
>          printf("Unknown option: %s\r\n", name);
>         exit(1);
>      }
>
> We don't really need 'if else' either. (I'm sure I didn't have that in
> Fortran years ago.)
>

Sometimes that is a preferable arrangement. But this is a straw man
(whataboutism, if you prefer) - we have "if ... else ..." in C. A key
difference is that "if ... else ..." is extremely useful, and extremely
common in most imperative languages. A "for ... else ..." construct is
only occasionally useful - as for all constructs, that usefulness must
be weighed against the additional learning and the potential for
confusion. For "if ... else ...", the balance is clearly on the
"useful" side. For "for ... else ...", the balance would appear to be
on the "confusing" side.

(Before you bring in more whataboutisms, C already has constructs that
are more often confusing than useful. There is no need to mention them
or list them.)

>
>> A smaller and more compact solution could be nice - but that would, as
>> you say, require a higher level language.
>
> By higher level I mean something like:
>
>     do_option(name in optionnames, value)
>
> 'in' would return 1..len or 0, so do_option would have to deal with a
> option code of 0, to keep it simple.
>

That is an odd construct - I would not expect to see it, or immediately
know what was meant. I would rather expect something like:

if (name in optionnames) {
do_option(optionnames[name], value);
} else {
print "Unknown option"
}

or

try {
do_option(optionnames[name], value);
} except on key_error {
print "Unknown option"
}

(I am making up high-level syntax as I go here.)



> Higher level code tends not to have so many explicit loops, but so long
> as for-loops /are/ still used, then an 'else' clause containing
> dedicated code for a normal termination rather than a break will still
> be useful.
>

I don't disagree that a "for ... else ..." clause could be useful. But
I am not convinced that it is useful enough to overcome potential
confusion - I think its use would be rare, and alternative simple and
clear solutions are easily available.

Bart

unread,
Apr 17, 2019, 10:05:12 AM4/17/19
to
On 17/04/2019 13:05, David Brown wrote:
> On 17/04/2019 12:38, Bart wrote:

> bool found = false;
> int sw;
> for (sw = 0; sw < len; sw++) {
> if (strcmp(name, options[sw]) == 0) {
> found = true;
> break;
> }
> }
> if (found) {
> do_option(sw, value);

Another point is that as written, 'sw' has to be known outside so cannot
be local to the loop. (In my example, the code to be executed can be
brought inside the loop body; sometimes it can't. But 'found' needs to
be outside.)

Alf P. Steinbach

unread,
Apr 17, 2019, 10:15:46 AM4/17/19
to
On 17.04.2019 14:57, Bart wrote:
> [snip]
> By higher level I mean something like:
>
>     do_option(name in optionnames, value)
>
> 'in' would return 1..len or 0, so do_option would have to deal with a
> option code of 0, to keep it simple.

That places responsibility for dealing with an unknown option down in
`do_option`, via an `if` or `switch` or something.

I'd rather have that logic up front:

if( const int id = 1 + my::index_of( name, optionnames ) ) {
app::do_option( id, value );
} else {
my::fail( ""s + __func__ + " - unknown option '" + name + "'" );
}

> Higher level code tends not to have so many explicit loops, but so long
> as for-loops /are/ still used, then an 'else' clause containing
> dedicated code for a normal termination rather than a break will still
> be useful.

Consider also

[](){
for( const& string item: optionnames ) {
if( item == name ) {
app::do_option( 1 + &item - &optionnames[0], value );
return;
}
}
my::fail( ""s + __func__ + " - unknown option '" + name + "'" );
}();

`return` is a nice statement; here it returns to the point after the
last semicolon.

The perhaps currently cryptic JavaScript-like start and end of that code
is a pattern that one might as well get used to.


Cheers!,

- Alf

Bart

unread,
Apr 17, 2019, 10:17:05 AM4/17/19
to
On 17/04/2019 14:36, David Brown wrote:
> On 17/04/2019 14:57, Bart wrote:

>> By higher level I mean something like:
>>
>>     do_option(name in optionnames, value)
>>
>> 'in' would return 1..len or 0, so do_option would have to deal with a
>> option code of 0, to keep it simple.
>>
>
> That is an odd construct - I would not expect to see it, or immediately
> know what was meant.

Python has "in" but just returns True or False, which is not very
helpful. My version returns the index where a occurs in b, although most
useful for 1-based lists.

I would rather expect something like:
>
> if (name in optionnames) {
> do_option(optionnames[name], value);

You're doing a double lookup, one with "in", and one with [], which
latter would require optionnames to be a dict. It gets messy.


> I don't disagree that a "for ... else ..." clause could be useful. But
> I am not convinced that it is useful enough to overcome potential
> confusion - I think its use would be rare, and alternative simple and
> clear solutions are easily available.

It's rare partly because languages don't have it!

Although I already said it would have problems being added to C because
of the ambiguity of 'else'.


Alf P. Steinbach

unread,
Apr 17, 2019, 10:24:34 AM4/17/19
to
Oh, sorry, for the start I meant

[&](){

capturing reference to the variables.

And also failed to mention, as before in this thread, the multi-line
lambda indicates to me that it should probably be a named function.


Cheers!,

- Alf

Rick C. Hodgin

unread,
Apr 17, 2019, 10:54:11 AM4/17/19
to
On 4/17/2019 10:05 AM, Bart wrote:
>>     bool found = false;
>>     int sw;
>>     for (sw = 0; sw < len; sw++) {
>>         if (strcmp(name, options[sw]) == 0) {
>>             found = true;
>>             break;
>>         }
>>     }
>>     if (found) {
>>         do_option(sw, value);
>
> Another point is that as written, 'sw' has to be known outside so cannot be
> local to the loop. (In my example, the code to be executed can be brought
> inside the loop body; sometimes it can't. But 'found' needs to be outside.)

In CAlive, I have setup flow control blocks for this type of logic. It
prevents many types of temporary variables, and allows structured flow
movement without using gotos or something else complex:

bool found = false;
int sw;

flow
{
for (sw = 0; sw < len; sw++
{
if (strcmp(name, options[sw]) == 0)
flowto found;
}

} found {
do_option(sw, value);
}

If you need to return back to where you were after "calling found," you
would use the () syntax. You can also pass in parameters if they are
defined:

flowto found(); // Run the code, and return

--
Rick C. Hodgin

David Brown

unread,
Apr 17, 2019, 11:52:14 AM4/17/19
to
On 17/04/2019 16:16, Bart wrote:
> On 17/04/2019 14:36, David Brown wrote:
>> On 17/04/2019 14:57, Bart wrote:
>
>>> By higher level I mean something like:
>>>
>>>      do_option(name in optionnames, value)
>>>
>>> 'in' would return 1..len or 0, so do_option would have to deal with a
>>> option code of 0, to keep it simple.
>>>
>>
>> That is an odd construct - I would not expect to see it, or immediately
>> know what was meant.
>
> Python has "in" but just returns True or False, which is not very
> helpful. My version returns the index where a occurs in b, although most
> useful for 1-based lists.
>
>   I would rather expect something like:
>>
>>     if (name in optionnames) {
>>         do_option(optionnames[name], value);
>
> You're doing a double lookup, one with "in", and one with [], which
> latter would require optionnames to be a dict. It gets messy.
>

Depending on the data structure, the double lookup would often be fast
and simple. In a compiled language with an optimising compiler,
especially in a language which emphasises immutable data (as most modern
languages do), this could be optimised.

However, this is just sample code in an imaginary language - not
something we need to figure out in detail.

>
>> I don't disagree that a "for ... else ..." clause could be useful.  But
>> I am not convinced that it is useful enough to overcome potential
>> confusion - I think its use would be rare, and alternative simple and
>> clear solutions are easily available.
>
> It's rare partly because languages don't have it!
>

Mostly it is rare because you don't usually need it.

David Brown

unread,
Apr 17, 2019, 11:55:49 AM4/17/19
to
On 17/04/2019 16:15, Alf P. Steinbach wrote:
> On 17.04.2019 14:57, Bart wrote:
>> [snip] By higher level I mean something like:
>>
>>      do_option(name in optionnames, value)
>>
>> 'in' would return 1..len or 0, so do_option would have to deal with a
>> option code of 0, to keep it simple.
>
> That places responsibility for dealing with an unknown option down in
> `do_option`, via an `if` or `switch` or something.
>
> I'd rather have that logic up front:
>
>     if( const int id = 1 + my::index_of( name, optionnames ) ) {
>         app::do_option( id, value );
>     } else {
>         my::fail( ""s + __func__ + " - unknown option '" + name + "'" );
>     }
>

Maybe a std::optional would be a nicer choice here, rather than having
to manipulate the index number?


Bart

unread,
Apr 17, 2019, 12:44:44 PM4/17/19
to
On 17/04/2019 15:15, Alf P. Steinbach wrote:
> On 17.04.2019 14:57, Bart wrote:
>> [snip] By higher level I mean something like:
>>
>>      do_option(name in optionnames, value)
>>
>> 'in' would return 1..len or 0, so do_option would have to deal with a
>> option code of 0, to keep it simple.
>
> That places responsibility for dealing with an unknown option down in
> `do_option`, via an `if` or `switch` or something.

Yeah, I forgot about the error message. Then do_option can just return
the option number (or a status), and it becomes necessary to wrap withe
do_option call with an 'if'.

Dealing with option 0 inside do_option is not an issue (in my code it
means adding one line 'when 0 then' to ignore that option number).

> Consider also
>
> [](){
> for( const& string item: optionnames ) {
> if( item == name ) {
> app::do_option( 1 + &item - &optionnames[0], value );
> return;
> }
> }
> my::fail( ""s + __func__ + " - unknown option '" + name + "'" );
> }();
>
> `return` is a nice statement; here it returns to the point after the
> last semicolon.

So, defining an anonymous function, immediately calling it, and using
'return' to provide the alternate exit path from the loop that is required.

Clever, but the kind of cleverness that obscures the mundane task that
we are actually trying to do [in this case].

As a reminder, here's my original for-else example in a bigger context:

https://pastebin.com/Yv6xQg92

(This processes command-line options and filenames for a C compiler, but
the same pattern is used in several programs.)

This bit doesn't need to be fast, and it doesn't need to be clever.

Bart

unread,
Apr 17, 2019, 12:48:41 PM4/17/19
to
On 17/04/2019 17:44, Bart wrote:

> As a reminder, here's my original for-else example in a bigger context:
>
>   https://pastebin.com/Yv6xQg92

(That guest paste doesn't seem to work. Try:

https://pastebin.com/raw/bNUNjk4K)

Ben Bacarisse

unread,
Apr 17, 2019, 3:43:55 PM4/17/19
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
<cut>
> Going back even further historically, C's parent language BCPL had a
> construct that one can think of as “arithmetic blocks”, like a curly
> braces compound statement that could return a value.

Yes. VALOF $( ... RESULTIS exp ... $) to be precise, with later
versions permitting { } in place of $( $). And of course gcc has
statement expressions as an extension, though you can't "return" from
them at arbitrary points -- the last expression is the value.

> And expressing your example with an imaginary such construct it's not
> significantly more to read, and then properly constrains the scope
> where the code can change or influence the value of the boolean:
>
> const bool loop_body_executed = BCPL_BLOCK {
> for (INIT; COND; UPDATE) {
> if( SOMETHING ) { return true; }
> }
> return false;
> };
> if( not loop_body_executed ) {
> // The logical "else" part.
> }

This is not the example I had, so the bool is not well named as it is
not always set true when the loop body executes.

I can't think of a neat way to avoid using the flag variable, even
using BCPL-style VALOF:

IF NOT VALOF $(
LET loop_body_executed = FALSE
FOR N = 1 TO MAX DO $(
code; code; loop_body_executed := TRUE
$)
RESULTIS loop_body_executed
$) DO $(
// original else part
$)

But there's a bigger problem with both this and the lambda version:
neither abstracts over the loop code. You write it fresh each time.
And you can't even cut and paste the loop code because 'break's have to
turn into 'return true'.

In Lisp you could write a macro for both meanings where the user simply
has to supply the loop code and the 'else' code.

> As it happens it's easy to define a macro like BCPL_BLOCK:
>
> #define BCPL_BLOCK ~[&]()
>
> ... where the `~` operator is defined as e.g.
>
> namespace lambda_support
> {
> using std::declval;
>
> template< class Func
> , class Enabled_ = decltype( declval<Func>()() )
> >
> inline auto operator~( Func const& f )
> -> decltype(( f() ))
> { return f(); }
> } // namespace lambda_support

This seems to be getting close to what I'd call lambda abuse. Is it
worth it to avoid the () that you'd otherwise need? I realise I may be
missing the point, though.

<cut>
--
Ben.

David Brown

unread,
Apr 18, 2019, 6:21:46 AM4/18/19
to
Since it doesn't need to be fast, and doesn't need to be clever, what's
wrong with existing C solutions - using a boolean flag, splitting things
out into a separate function (you can give it a name, rather than using
a lambda), or using a goto ?

If your language already has a "for/else" statement, then it's fair
enough to use it, of course.

Bart

unread,
Apr 18, 2019, 1:54:35 PM4/18/19
to
Well, this is the point really. If a feature is reliably available, then
you just use it because it's convenient. Instead of inventing a solution
or rewriting a pattern you've used 100s of times before.

Same argument with the subject of multi-level loop breaks. If writing C,
then every so often you have to stop and work around the problem of C
having only single-level breaks. (Or zero-level breaks if you happen to
be inside 'switch'.)

(Last time I used a multi-level break: about 15 minutes ago. I wasn't
writing C. It's just there. What I could also have done with actually
was 'while-else' that I haven't got. So something to ponder adding.)

Ben Bacarisse

unread,
Apr 18, 2019, 3:37:09 PM4/18/19
to
Bart <b...@freeuk.com> writes:

> ... If writing C, then every so often you have to stop and work around
> the problem of C having only single-level breaks.

This is a case where what you call a work-around, I call an improvement.

I like to reason about code, and the label helps a lot. I often ask,
particularly after loops, "what is the state of the program at this
point?" and the presence of a label is a big red flag telling me I have
to take partial execution into account. It's not a huge advantage,
since multi-level breaks are rare and likely to be flagged by a big
comment, but the label is in just the right place for me to know I have
to think about gotos.

--
Ben.

Bart

unread,
Apr 18, 2019, 4:15:32 PM4/18/19
to
In my codebase (only compiled code not scripting), about 2.5% of all
'breaks' are multi-level ones (and break is used very commonly in my
code to terminate endless loops), and 2% of all 'for' loops are 'for-else'.

It doesn't sound much, but DB mentioned once that only 1.5% of his C
for-loops were anything other simple iteration via a variable. Yet C
loops still allow all those other possibilities, things that can make
code harder to reason about, not easier.


0 new messages