Discussion:
else ladders practice
Add Reply
fir
2024-10-31 12:11:30 UTC
Reply
Permalink
somethins i got such pices of code like

if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}

technically i would need to add elses - but the question is if to do that

do teh code has really chance to be slower without else (except some
very prmitive compilers) ??

not adding else makes liek code shorter.. so im not literally sure which
is better
Anton Shepelev
2024-10-31 13:15:45 UTC
Reply
Permalink
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
but the question is if to do that o teh code has really
chance to be slower without else (except some very
prmitive compilers) ?? ot adding else makes liek code
shorter.. so im not literally sure which is better
I am all for code literalism, and want it to emphasize the
most natural method of execution. Therefore, I usually
append `return' or `goto' to each then-block in an tabular
if-sequence as above. I believe the perormance will depend
on the compiler.
--
() ascii ribbon campaign -- against html e-mail
/\ www.asciiribbon.org -- against proprietary attachments
fir
2024-10-31 14:23:52 UTC
Reply
Permalink
Post by Anton Shepelev
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
becouse its more strict expressed with else, with else
compiler is informed to drop all teh elese if its found

without else it is strictly liek it should test all teh ifs each time..

though in practice it would be liek that if compiler only compiles line
by line in separation, if it has a view on whole procedure it is clear
that those branchhes exclude itself so teh assembly generated should not
generate such tests but put soem goto itself
Post by Anton Shepelev
but the question is if to do that o teh code has really
chance to be slower without else (except some very
prmitive compilers) ?? ot adding else makes liek code
shorter.. so im not literally sure which is better
I am all for code literalism, and want it to emphasize the
most natural method of execution. Therefore, I usually
append `return' or `goto' to each then-block in an tabular
if-sequence as above. I believe the perormance will depend
on the compiler.
James Kuyper
2024-10-31 18:16:23 UTC
Reply
Permalink
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
He has indicated that the value of n is not changed inside any of the
if-clauses. A sufficiently sophisticated compiler could notice that
fact, and also that each of the conditions is on the same variable, and
as a result it could generate the same kind of code as if it had been
written with 'else', so it won't generate unnecessary condition tests.
It might, in fact, generate the same kind of code which would have been
generated if it had been coded properly, as a switch statement, so it
might use a jump table, if appropriate.
But it's better to write it as a switch statement in the first place, so
you don't have to rely upon the compiler being sufficiently
sophisticated to get the best results.
David Brown
2024-11-01 08:56:56 UTC
Reply
Permalink
Post by James Kuyper
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
He has indicated that the value of n is not changed inside any of the
if-clauses. A sufficiently sophisticated compiler could notice that
fact, and also that each of the conditions is on the same variable, and
as a result it could generate the same kind of code as if it had been
written with 'else', so it won't generate unnecessary condition tests.
It might, in fact, generate the same kind of code which would have been
generated if it had been coded properly, as a switch statement, so it
might use a jump table, if appropriate.
But it's better to write it as a switch statement in the first place, so
you don't have to rely upon the compiler being sufficiently
sophisticated to get the best results.
I disagree entirely.

It is best to write the code in the way that makes most sense - whatever
gives the best clarity and makes the programmer's intentions obvious to
readers, and with the least risk of errors. Consider the
maintainability of the code - is it likely to be changed in the future,
or adapted and re-used in other contexts? If so, that should be a big
influence on how you structure the source code. Can a different
structure make it less likely for errors to occur unnoticed? For
example, if the controlling value can be an enumeration then with a
switch, a good compiler can check if there are accidentally unhandled
cases (and even a poor compiler can check for duplicates).

But details of the efficiency of the generated object code, especially
on weaker compilers, should not be a consideration unless you have
measured the speed of the code, found it slower than the requirements,
and identified the code section that can be made significantly more
efficient by re-structuring.


I would rather say that you /should/ rely on the compiler being
sophisticated, if code speed is important to your task. Let the
compiler handle the little things, so that the programmer can
concentrate on the big things - clear code, correct code, maintainable
code, low-risk code, and efficient /algorithms/.

I am sure you would not advocate for using #define'd constants instead
of "const" values, or function-like macros instead of inline functions,
or goto's instead of "for" loops, or re-using a "register int temp1;"
variable defined at the top of a function instead of block-local
appropriately named variables. All these things can give more efficient
results on weak compilers - but all are detrimental to code quality, and
that's what you should put first.


In practice, I probably /would/ structure the code as a switch rather
than a series of "if" statements, unless I had overriding reason to do
otherwise. But /not/ because of efficiency of the results.

Without all the details of the OP's code, it is of course impossible to
be sure what structure is clearest for his code.
James Kuyper
2024-11-02 02:25:56 UTC
Reply
Permalink
Post by David Brown
Post by James Kuyper
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
He has indicated that the value of n is not changed inside any of the
if-clauses. A sufficiently sophisticated compiler could notice that
fact, and also that each of the conditions is on the same variable, and
as a result it could generate the same kind of code as if it had been
written with 'else', so it won't generate unnecessary condition tests.
It might, in fact, generate the same kind of code which would have been
generated if it had been coded properly, as a switch statement, so it
might use a jump table, if appropriate.
But it's better to write it as a switch statement in the first place, so
you don't have to rely upon the compiler being sufficiently
sophisticated to get the best results.
I disagree entirely.
It is best to write the code in the way that makes most sense -
whatever gives the best clarity and makes the programmer's intentions
obvious to readers, and with the least risk of errors. Consider the
maintainability of the code - is it likely to be changed in the
future, or adapted and re-used in other contexts? If so, that should
be a big influence on how you structure the source code. Can a
different structure make it less likely for errors to occur unnoticed?
For example, if the controlling value can be an enumeration then with
a switch, a good compiler can check if there are accidentally
unhandled cases (and even a poor compiler can check for duplicates).
I don't see those criteria as conflicting with my advice. A switch seems
to me to unambiguously the clearest way of writing this logic, for
precisely the same reason it also makes it easier for unsophisticated
compilers to optimize it - what needs to be done is clearer both to the
compiler and to the human reader.
David Brown
2024-11-02 10:44:02 UTC
Reply
Permalink
Post by James Kuyper
Post by David Brown
Post by James Kuyper
somethins i got such pies of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses
Why?
He has indicated that the value of n is not changed inside any of the
if-clauses. A sufficiently sophisticated compiler could notice that
fact, and also that each of the conditions is on the same variable, and
as a result it could generate the same kind of code as if it had been
written with 'else', so it won't generate unnecessary condition tests.
It might, in fact, generate the same kind of code which would have been
generated if it had been coded properly, as a switch statement, so it
might use a jump table, if appropriate.
But it's better to write it as a switch statement in the first place, so
you don't have to rely upon the compiler being sufficiently
sophisticated to get the best results.
I disagree entirely.
It is best to write the code in the way that makes most sense -
whatever gives the best clarity and makes the programmer's intentions
obvious to readers, and with the least risk of errors. Consider the
maintainability of the code - is it likely to be changed in the
future, or adapted and re-used in other contexts? If so, that should
be a big influence on how you structure the source code. Can a
different structure make it less likely for errors to occur unnoticed?
For example, if the controlling value can be an enumeration then with
a switch, a good compiler can check if there are accidentally
unhandled cases (and even a poor compiler can check for duplicates).
I don't see those criteria as conflicting with my advice. A switch seems
to me to unambiguously the clearest way of writing this logic, for
precisely the same reason it also makes it easier for unsophisticated
compilers to optimize it - what needs to be done is clearer both to the
compiler and to the human reader.
It's not the advice itself that I disagree with - it is the /reason/ for
the advice.

Coding always has some trade-offs, and how you weight different factors
such as portability and code efficiency depends on the task at hand.
Unless you know there are other more important factors, the appropriate
balance for most code is to put the emphasis on code clarity over code
efficiency, as that typically improves your chances of getting it
correct. (And correct results always trump fast results.)

As a bonus, writing code clearly - from a human programmer perspective -
often results in code that a compiler can optimise well. But that's not
the motivation.


Your advice was to write a switch because compilers can generate more
efficient code even if the compiler is not particularly sophisticated.
That might be the right advice, but it is the wrong reasoning.


The OP should write the code that is /clearest/ for the task in hand.
It is certainly likely that a switch is often clearest for this kind of
structure - but perhaps something else fits better for the OP's purpose.

Or if code efficiency really is an issue for the OP's task, then he
should write it in half a dozen different ways, test them, and compare
the speed in reality.
fir
2024-11-02 08:37:35 UTC
Reply
Permalink
Post by David Brown
It is best to write the code in the way that makes most sense - whatever
gives the best clarity and makes the programmer's intentions obvious to
readers, and with the least risk of errors.
the fact is it is somewhat hard to say which is more obvious to readers


if(key=='A') Something();
else if(key=='B') Something();
else if(key=='C') Something();
else if(key=='D') Something();

or

if(key=='A') Something();
if(key=='B') Something();
if(key=='C') Something();
if(key=='D') Something();

imo the second is more for human but logically its a bit diferent
becouse else chain only goes forward on "false" and new statemant on
both "true and false"

(there is also an option to go only on true (something like "then" keyword

if(key=='A') Something();
then if(key=='B') Something();
then if(key=='C') Something();
then if(key=='D') Something();

though c dont has it (eventually can be done by nestong ifs)
David Brown
2024-11-02 10:51:56 UTC
Reply
Permalink
Post by fir
Post by David Brown
It is best to write the code in the way that makes most sense - whatever
gives the best clarity and makes the programmer's intentions obvious to
readers, and with the least risk of errors.
the fact is it is somewhat hard to say which is more obvious to readers
if(key=='A') Something();
else if(key=='B') Something();
else if(key=='C') Something();
else if(key=='D') Something();
or
if(key=='A') Something();
if(key=='B') Something();
if(key=='C') Something();
if(key=='D') Something();
imo the second is more for human but logically its a bit diferent
becouse else chain only goes forward on "false" and new statemant on
both "true and false"
You might also split up your functions differently, so that you can write :

if (key == 'A') return Something_A();
if (key == 'B') return Something_B();
if (key == 'C') return Something_C();
if (key == 'D') return Something_D();


That can be clear to the reader, and has the same effect as "else if".

(You might also consider using the space key more - it adds
significantly to code legibility. It's not by chance that it is the
biggest key on the keyboard.)
James Kuyper
2024-11-02 13:11:47 UTC
Reply
Permalink
On 02/11/2024 09:37, fir wrote:
...
Post by fir
the fact is it is somewhat hard to say which is more obvious to readers
if(key=='A') Something();
else if(key=='B') Something();
else if(key=='C') Something();
else if(key=='D') Something();
or
if(key=='A') Something();
if(key=='B') Something();
if(key=='C') Something();
if(key=='D') Something();
imo the second is more for human but logically its a bit diferent
becouse else chain only goes forward on "false" and new statemant on
both "true and false"
If I saw code written the second way, I would ask myself why it wasn't
written the first way. The most obvious reason for not writing it the
first way is that the calls to Something() might indirectly change the
value of 'key', and that the later if() conditions are intentionally
dependent on those changes. I would waste time confirming that there's
no such possibility. You should write your code so that others reading
it won't have to worry about that possibility.
Richard Harnden
2024-10-31 13:25:36 UTC
Reply
Permalink
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
Why not ...

switch (n)
{
case 1:
/* something */
break;

case 2:
/* etc ... */

default:
/* something else */
}

... ?
Post by fir
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
not adding else makes liek code shorter.. so im not literally sure which
is better
The size of teh [sic] source code won't make any difference to the size
of the executable - so aim for readability first and foremost.
fir
2024-10-31 13:48:37 UTC
Reply
Permalink
Post by Richard Harnden
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
Why not ...
switch (n)
{
/* something */
break;
/* etc ... */
/* something else */
}
... ?
switch is literally flawed construction - (i was even writing or at
kleast thinking why, last time but sadli i literrally forgot my
arguments, would need to find that notes)

so i forgot why but i forgiot that it is flawed - i mean the form of
this c switch has sme error
Post by Richard Harnden
Post by fir
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
not adding else makes liek code shorter.. so im not literally sure
which is better
The size of teh [sic] source code won't make any difference to the size
of the executable - so aim for readability first and foremost.
fir
2024-10-31 14:17:44 UTC
Reply
Permalink
Post by fir
Post by Richard Harnden
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
Why not ...
switch (n)
{
/* something */
break;
/* etc ... */
/* something else */
}
... ?
switch is literally flawed construction - (i was even writing or at
kleast thinking why, last time but sadli i literrally forgot my
arguments, would need to find that notes)
so i forgot why but i forgiot that it is flawed - i mean the form of
this c switch has sme error
ok i found it was here in a thread freeform switch case
i was noted that swich shoudl look like



switch(a);
case(1) {}
case(2) {}
case(3) {}
case(4) {}
case(5) {}


i mean thuis enclosing brackets are wrong..and makes thic construction
hard to understand


here its cleat case simply work witch last swich (though this could be
understood in few ways, liek it could be last in runtime or last in
top-down listing etc

also betetr word for switch is probably "select"

also case probably culd have 2 forms

case(1) { }
case(2) { }

dont needs break as its exclosive execution


case 1:
case 2:
case 3:

could eventually work like goto and fall through
(though im not sure what is needed and what not)

overally the one thing is rather sure those enclosing {} are wrong
and that swich case clausule in generall could have many variations
not only one
(this fallthru one probably should have name closer to goto
fir
2024-10-31 14:43:39 UTC
Reply
Permalink
Post by fir
switch(a);
case(1) {}
case(2) {}
case(3) {}
case(4) {}
case(5) {}
in fact thise if else if else if else laddars also suck imo so in fact
this switch is more logical from some point of view and if so those
above would be probably most suitable probably working goto way

though meybe thsi also could be replaaced by soem ad-hoc defined
dictionary and a key-value
Bonita Montero
2024-10-31 14:25:34 UTC
Reply
Permalink
Post by Richard Harnden
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
Why not ...
switch (n)
{
        /* something */
        break;
    /* etc ... */
        /* something else */
std::unreachable();
Post by Richard Harnden
}
... ?
Post by fir
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
not adding else makes liek code shorter.. so im not literally sure
which is better
The size of teh [sic] source code won't make any difference to the size
of the executable - so aim for readability first and foremost.
Dan Purgert
2024-10-31 14:14:49 UTC
Reply
Permalink
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
In the above, all conditionals are always checked -- that is the truth
of a previous conditional statement has no bearing on subsequent tests.
This leads to the potential of tests going off in directions you hadn't
necessarily anticipated.

However, 'if..elseif..else' will only check subsequent conditionals if
the prior statements were false. So for the case that "n=2", you're
only ever testing the two cases "if (n==1)" (which is false) and
"elseif(n==2)". The computer just skips to the end of the set of
statements.


Given this MWE (my own terrible code aside ;) ):

int main(){
int n=0;
printf ("all if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
if (n==1) { printf ("n: %u\n",n); n++;}
if (n==2) { printf ("n: %u\n",n); n++;}
if (n==3) { printf ("n: %u\n",n); n++;}
if (n==4) { printf ("n: %u\n",n); n++;}
printf ("all if completed, n=%u\n",n);

n=3;
printf ("with else if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
else if (n==1) { printf ("n: %u\n",n); n++;}
else if (n==2) { printf ("n: %u\n",n); n++;}
else if (n==3) { printf ("n: %u\n",n); n++;}
else { printf ("n: %u\n",n); n++;}
printf ("with else if completed, n=%u\n",n);
}


You'll get the output:

all if, n=0
n: 0
n: 1
n: 2
n: 3
n: 4
all if completed, n=5
with else if, n=3
n: 3
with else if completed, n=4

HTH :)
--
|_|O|_|
|_|_|O| Github: https://github.com/dpurgert
|O|O|O| PGP: DDAB 23FB 19FA 7D85 1CC1 E067 6D65 70E5 4CE7 2860
fir
2024-10-31 14:29:34 UTC
Reply
Permalink
Post by Dan Purgert
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
In the above, all conditionals are always checked -- that is the truth
of a previous conditional statement has no bearing on subsequent tests.
This leads to the potential of tests going off in directions you hadn't
necessarily anticipated.
However, 'if..elseif..else' will only check subsequent conditionals if
the prior statements were false. So for the case that "n=2", you're
only ever testing the two cases "if (n==1)" (which is false) and
"elseif(n==2)". The computer just skips to the end of the set of
statements.
int main(){
int n=0;
printf ("all if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
if (n==1) { printf ("n: %u\n",n); n++;}
if (n==2) { printf ("n: %u\n",n); n++;}
if (n==3) { printf ("n: %u\n",n); n++;}
if (n==4) { printf ("n: %u\n",n); n++;}
printf ("all if completed, n=%u\n",n);
n=3;
printf ("with else if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
else if (n==1) { printf ("n: %u\n",n); n++;}
else if (n==2) { printf ("n: %u\n",n); n++;}
else if (n==3) { printf ("n: %u\n",n); n++;}
else { printf ("n: %u\n",n); n++;}
printf ("with else if completed, n=%u\n",n);
}
all if, n=0
n: 0
n: 1
n: 2
n: 3
n: 4
all if completed, n=5
with else if, n=3
n: 3
with else if completed, n=4
HTH :)
i not modify n in those {} blocks so this example is not much relevant

my quiestion is more liek what is a metter of beter style

from my youth i roughly always added those elses notw i consider not to
write them
Dan Purgert
2024-10-31 14:34:46 UTC
Reply
Permalink
Post by fir
Post by Dan Purgert
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
In the above, all conditionals are always checked -- that is the truth
of a previous conditional statement has no bearing on subsequent tests.
This leads to the potential of tests going off in directions you hadn't
necessarily anticipated.
However, 'if..elseif..else' will only check subsequent conditionals if
the prior statements were false. So for the case that "n=2", you're
only ever testing the two cases "if (n==1)" (which is false) and
"elseif(n==2)". The computer just skips to the end of the set of
statements.
int main(){
int n=0;
printf ("all if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
if (n==1) { printf ("n: %u\n",n); n++;}
if (n==2) { printf ("n: %u\n",n); n++;}
if (n==3) { printf ("n: %u\n",n); n++;}
if (n==4) { printf ("n: %u\n",n); n++;}
printf ("all if completed, n=%u\n",n);
n=3;
printf ("with else if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
else if (n==1) { printf ("n: %u\n",n); n++;}
else if (n==2) { printf ("n: %u\n",n); n++;}
else if (n==3) { printf ("n: %u\n",n); n++;}
else { printf ("n: %u\n",n); n++;}
printf ("with else if completed, n=%u\n",n);
}
all if, n=0
n: 0
n: 1
n: 2
n: 3
n: 4
all if completed, n=5
with else if, n=3
n: 3
with else if completed, n=4
HTH :)
i not modify n in those {} blocks so this example is not much relevant
I'm using that as a simplified case to force the issue. "n" could be
modified anywhere, just so long as it is "between" any two of the test
cases being checked.
Post by fir
my quiestion is more liek what is a metter of beter style
If it is a series of related conditions, then "if .. else if .. else".
--
|_|O|_|
|_|_|O| Github: https://github.com/dpurgert
|O|O|O| PGP: DDAB 23FB 19FA 7D85 1CC1 E067 6D65 70E5 4CE7 2860
fir
2024-10-31 14:49:11 UTC
Reply
Permalink
Post by Dan Purgert
Post by fir
Post by Dan Purgert
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
In the above, all conditionals are always checked -- that is the truth
of a previous conditional statement has no bearing on subsequent tests.
This leads to the potential of tests going off in directions you hadn't
necessarily anticipated.
However, 'if..elseif..else' will only check subsequent conditionals if
the prior statements were false. So for the case that "n=2", you're
only ever testing the two cases "if (n==1)" (which is false) and
"elseif(n==2)". The computer just skips to the end of the set of
statements.
int main(){
int n=0;
printf ("all if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
if (n==1) { printf ("n: %u\n",n); n++;}
if (n==2) { printf ("n: %u\n",n); n++;}
if (n==3) { printf ("n: %u\n",n); n++;}
if (n==4) { printf ("n: %u\n",n); n++;}
printf ("all if completed, n=%u\n",n);
n=3;
printf ("with else if, n=%u\n",n);
if (n==0) { printf ("n: %u\n",n); n++;}
else if (n==1) { printf ("n: %u\n",n); n++;}
else if (n==2) { printf ("n: %u\n",n); n++;}
else if (n==3) { printf ("n: %u\n",n); n++;}
else { printf ("n: %u\n",n); n++;}
printf ("with else if completed, n=%u\n",n);
}
all if, n=0
n: 0
n: 1
n: 2
n: 3
n: 4
all if completed, n=5
with else if, n=3
n: 3
with else if completed, n=4
HTH :)
i not modify n in those {} blocks so this example is not much relevant
I'm using that as a simplified case to force the issue. "n" could be
modified anywhere, just so long as it is "between" any two of the test
cases being checked.
bot not in the cases i got on mind (i wrote its if elseelse else elese
case but with else just skipped

probbaly people use switch in such case but i distasted switch so i
iused else ladders, now i think if to use just ifs
Post by Dan Purgert
Post by fir
my quiestion is more liek what is a metter of beter style
If it is a series of related conditions, then "if .. else if .. else".
Janis Papanagnou
2024-10-31 19:24:42 UTC
Reply
Permalink
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
not adding else makes liek code shorter.. so im not literally sure which
is better
The language (and the compiler) is there to serve me, not vice versa.

While I expect modern compilers to do optimizations you generally
cannot rely on that.

I'm not much interested in doing peep-hole optimizations, that's the
task of the compilers.

There's optimizations on the logic that is of more concern (to me),
and often has even a greater impact on performance.

I use the constructs that serve me best to express what I intend to
implement. (But I have to use what the respective language supports.)

There's (in various languages) different ways to implement cascades;
three way decisions (<0, =0, >0), arrays of switch branches triggered
by a positive integer (1, 2, 3, ...), a case/switch whose argument
is visibly evaluated just once, if-cascades that allow for multiple
possible conditions, and with exclusive conditions. You can also
implement hierarchical cascades considering the assumed distribution
of appearing values (n<3, n>4, ...), or, with unknown distribution,
binary hierarchical nesting of the if-tests. And last but not least,
and depending on what the 'n' is algorithmically bound to, replacing
conditional cascades by OO polymorphism (where possible).

Of course I'd use 'else' to make the intention clear. I'd prefer an
'else-if', if the language supports that, to make things clearer and
the syntax construct simpler and more reliable.

In above example the 'switch' would probably be the obvious way to
write the case (assuming 'n' being an invariant in the if-cascade).
Although the necessity (in "C") for using 'break' makes it clumsy,
I have to admit, but since C is anyway not that abstract as language
I bite the bullet, since that is the most obvious fitting construct
here (as far as no more context is provided).

In a language like Awk you don't need the 'if' and write (e.g.)
n==1 { *something* ; break }
but you might also want to add a 'break' after "something" (as
shown) to explicitly prevent unnecessary further checks. (GNU Awk,
BTW, also supports a more powerful 'switch' that operates not only
on simple scalar data types, but also on strings and patterns.)

Janis
Bart
2024-10-31 21:33:40 UTC
Reply
Permalink
Post by fir
somethins i got such pices of code like
if(n==1) {/*something/}
if(n==2) {/*something/}
if(n==3) {/*something/}
if(n==4) {/*something/}
if(n==5) {/*something/}
technically i would need to add elses - but the question is if to do that
do teh code has really chance to be slower without else (except some
very prmitive compilers) ??
not adding else makes liek code shorter.. so im not literally sure which
is better
There are several clear patterns here: you're testing the same variable
'n' against several mutually exclusive alternatives, which also happen
to be consecutive values.

C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be used).

The closest way is to use 'switch', which is also likely to do all
comparisons at the same time (in O(1) time), if the compiler thinks
that's the best way to do it.

The next closest is to use an if-else chain, which is almost what you
have. But apparently you don't like writing 'else if' instead of 'if':

if(n==1) {/*something1*/}
else if(n==2) {/*something2*/}
else if(n==3) {/*something3*/}
else if(n==4) {/*something4*/}
else if(n==5) {/*something5*/}

This is actually cleaner than the 'break' you have to use with switch.

It also guarantees, whatever the compiler, that you don't waste time
doing the rest of the rests.
fir
2024-11-01 11:32:30 UTC
Reply
Permalink
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant foir
various approaches as it seems

imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)

so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,

as to those pointer tables im not sure but im like measurad it onece and
it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Bart
2024-11-01 12:03:51 UTC
Reply
Permalink
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant foir
various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece and
it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.

I also don't like the lack of exclusivity.

However I don't need to use C. If those 'somethings' were simple, or
were expressions, I could use syntax like this:

(n | s1, s2, s3, s4, s5)

If they were more elaborate statements, I would use a heavier syntax,
but still one where 'n' is only written once, and I don't need to repeat
'=='.

In the C version, you could mistakenly write 'm' instead of 'n', or '='
instead of '=='; it's more error prone, and a compiler might not be able
to detect it.

In the C, you could probably do something like this:

#define or else if

if (x == a) {}
or (x == b) {}
or (x == c) {}
fir
2024-11-01 12:55:46 UTC
Reply
Permalink
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
(n | s1, s2, s3, s4, s5)
on a C ground more suitable is

{s1,s2,s3,s4,s5)[n]

//which is just array indexing, and could use also like


{
{0,0,0,0,0}
{0,1,1,1,0}
{1,1,1,1,1}
{0,1,1,1,0}
{0,0,1,0,0}} [j][i]

anmd so on
(already wrote on thsi once)


bot for general switch something more is needed probably

i could cnsider something like

n -1-> /*something / -2-> /*something / -3-> /*something /

but i dont know (this above line seem not quite good
Post by Bart
If they were more elaborate statements, I would use a heavier syntax,
but still one where 'n' is only written once, and I don't need to repeat
'=='.
In the C version, you could mistakenly write 'm' instead of 'n', or '='
instead of '=='; it's more error prone, and a compiler might not be able
to detect it.
#define or else if
if (x == a) {}
or (x == b) {}
or (x == c) {}
Bart
2024-11-01 13:39:10 UTC
Reply
Permalink
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
    (n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.

You could try something like that in C:

int x;

x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];

printf("X=%d\n", x);

The output is:

a
b
c
d
X=40

Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
fir
2024-11-01 14:08:45 UTC
Reply
Permalink
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
(n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
int x;
x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
printf("X=%d\n", x);
a
b
c
d
X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
fir
2024-11-01 14:17:30 UTC
Reply
Permalink
Post by fir
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
(n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
int x;
x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
printf("X=%d\n", x);
a
b
c
d
X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
im surprised that it work, but in fact i meant that this syntax is old c
compatible but sych thing like


{printf("ONE"), printf("TWO"), printf("THREE")} [2]

shouldn evaluate al just the one is selected
like in array tab[23] not eveluates something other than tab[23]
Bart
2024-11-01 15:59:53 UTC
Reply
Permalink
Post by fir
Post by fir
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
    (n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
     int x;
     x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
     printf("X=%d\n", x);
    a
    b
    c
    d
    X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
im surprised that it work, but in fact i meant that this syntax is old c
compatible but sych thing like
{printf("ONE"), printf("TWO"), printf("THREE")} [2]
shouldn evaluate al just the one is selected
like in array tab[23] not eveluates something other than tab[23]
It's a 'compound literal'. It allows you to have the same {...}
initialisation data format, but anywhere, not just for initialing.
However it always needs a cast:

(int[]){printf("ONE"), printf("TWO"), printf("THREE")}[2];

This prints ONETWOTHREE, it also then indexes the 3rd value of the
array, which is 5, as returned by printf, so this:

printf("%d\n", (int[]){printf("ONE"), printf("TWO"),
printf("THREE")}[2]);

prints ONETWOTHREE5
David Brown
2024-11-01 17:35:24 UTC
Reply
Permalink
Post by Bart
Post by fir
Post by fir
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
    (n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
     int x;
     x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
     printf("X=%d\n", x);
    a
    b
    c
    d
    X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
im surprised that it work, but in fact i meant that this syntax is old
c compatible but sych thing like
{printf("ONE"), printf("TWO"), printf("THREE")} [2]
shouldn evaluate al just the one is selected
like in array tab[23] not eveluates something other than tab[23]
It's a 'compound literal'. It allows you to have the same {...}
initialisation data format, but anywhere, not just for initialing.
  (int[]){printf("ONE"), printf("TWO"), printf("THREE")}[2];
This prints ONETWOTHREE, it also then indexes the 3rd value of the
  printf("%d\n", (int[]){printf("ONE"), printf("TWO"),
printf("THREE")}[2]);
  prints ONETWOTHREE5
What you have written here is all correct, but a more common method
would be to avoid having three printf's :

void shout_a_number(int n) {
printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}

That's more likely to match what people would want.

Of course, you may need to sanity-check the value of "n" here!
Bart
2024-11-01 18:05:02 UTC
Reply
Permalink
Post by David Brown
Post by Bart
Post by fir
Post by fir
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
    (n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
     int x;
     x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
     printf("X=%d\n", x);
    a
    b
    c
    d
    X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
im surprised that it work, but in fact i meant that this syntax is
old c compatible but sych thing like
{printf("ONE"), printf("TWO"), printf("THREE")} [2]
shouldn evaluate al just the one is selected
like in array tab[23] not eveluates something other than tab[23]
It's a 'compound literal'. It allows you to have the same {...}
initialisation data format, but anywhere, not just for initialing.
   (int[]){printf("ONE"), printf("TWO"), printf("THREE")}[2];
This prints ONETWOTHREE, it also then indexes the 3rd value of the
   printf("%d\n", (int[]){printf("ONE"), printf("TWO"),
printf("THREE")}[2]);
   prints ONETWOTHREE5
What you have written here is all correct, but a more common method
void shout_a_number(int n) {
    printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each has
to have some side-effect to illustrate that.

A true N-way-select construct (C only really has ?:) would evaluate only
one, and would deal with an out-of-range condition.

(In my implementations, a default/else branch value must be provided if
the whole thing is expected to return a value.)
David Brown
2024-11-01 18:47:23 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Bart
Post by fir
Post by fir
Post by Bart
Post by fir
Post by Bart
Post by fir
ral clear patterns here: you're testing the same variable 'n' against
several mutually exclusive alternatives, which also happen to be
consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers or
even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it optically,
swich case i also dont like (use as far i i remember never in my code,
for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
as to those pointer tables im not sure but im like measurad it onece
and it was (not sure as to thsi as i dont remember exactly) slow maybe
dependant on architecture so its noth wort of use (if i remember correctly)
Well, personally I don't like that repetition, that's why I mentioned
the patterns. You're writing 'n' 5 times, '==' 5 times, and you're
writing out the numbers 1, 2, 3, 4, 5.
I also don't like the lack of exclusivity.
However I don't need to use C. If those 'somethings' were simple, or
    (n | s1, s2, s3, s4, s5)
on a C ground more suitable is
{s1,s2,s3,s4,s5)[n]
//which is just array indexing
No, it's specifically not array indexing, as only one of s1 - s5 is
evaluated, or nothing is when n is not in range, eg. n is 100.
     int x;
     x = ((int[]){(puts("a"),10), (puts("b"),20), (puts("c"), 30),
(puts("d"),40)})[3];
     printf("X=%d\n", x);
    a
    b
    c
    d
    X=40
Showing that all elements are evaluated first. If index is 100, the
result is also undefined.
:-O
what is this, first time i see such thing
im surprised that it work, but in fact i meant that this syntax is
old c compatible but sych thing like
{printf("ONE"), printf("TWO"), printf("THREE")} [2]
shouldn evaluate al just the one is selected
like in array tab[23] not eveluates something other than tab[23]
It's a 'compound literal'. It allows you to have the same {...}
initialisation data format, but anywhere, not just for initialing.
   (int[]){printf("ONE"), printf("TWO"), printf("THREE")}[2];
This prints ONETWOTHREE, it also then indexes the 3rd value of the
   printf("%d\n", (int[]){printf("ONE"), printf("TWO"),
printf("THREE")}[2]);
   prints ONETWOTHREE5
What you have written here is all correct, but a more common method
void shout_a_number(int n) {
     printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each has
to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would evaluate only
one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct. You are free to choose the
rules you want for your own language, but you are not free to dictate
what you think the rules should be for others. (You are welcome to
/opinions/, of course.)
Post by Bart
(In my implementations, a default/else branch value must be provided if
the whole thing is expected to return a value.)
OK, if that's what you want. My preference, if I were putting together
what /I/ thought was an idea language for /my/ use, would be heavy use
of explicit specifications and contracts for code, so that a
default/else branch is either disallowed (if there the selection covers
all legal values) or required (if the selection is abbreviated). A
default value "just in case" is, IMHO, worse than useless.

Different people, different preferences.
Bart
2024-11-01 19:47:53 UTC
Reply
Permalink
Post by David Brown
Post by Bart
Post by David Brown
What you have written here is all correct, but a more common method
void shout_a_number(int n) {
     printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each has
to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would evaluate
only one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct.
I don't think it's just opinion.

In general, an if-else-if chain (which was the point of the OP), would
evaluate only one branch. So would a switch-case construct if sensibly
implemented (in C's version, anything goes).

The same applies to C's c?a:b operator: only one of a or b is evaluated,
not both.

(This also why implementing if, switch, ?: via functions, which lots are
keen to do in the reddit PL forum, requires closures, lazy evaluation or
other advanced features.)
Post by David Brown
  You are free to choose the
rules you want for your own language, but you are not free to dictate
what you think the rules should be for others.  (You are welcome to
/opinions/, of course.)
Post by Bart
(In my implementations, a default/else branch value must be provided
if the whole thing is expected to return a value.)
OK, if that's what you want.  My preference, if I were putting together
what /I/ thought was an idea language for /my/ use, would be heavy use
of explicit specifications and contracts for code, so that a
default/else branch is either disallowed (if there the selection covers
all legal values) or required (if the selection is abbreviated).  A
default value "just in case" is, IMHO, worse than useless.
All such multiway constructs in my languages (there are 4, one of which
the job of both 'if' and C's ?:) have an optional else branch. A
missing 'else' has an notional 'void' type.

But it becomes mandatory if the whole thing returns a value, to satisfy
the type system, because otherwise it will try and match with 'void'.

SOMETHING needs to happen when none of the branches are executed; what
value would be returned then? The behaviour needs to be defined. You
don't want to rely on compiler analysis for this stuff.

In C on the other hand, the ':' of '?:' is always needed, even when it
is not expected to yield a value. Hence you often see this things like this:

p == NULL ? puts("error"): 0;

Here, gcc at least, also requires the types of the two branches to
match, even though the whole construct yields no common value. Meanwhile
I allow this (if I was keen on a compact form):

(p = nil | print "error")

No else is needed.
David Brown
2024-11-02 11:41:20 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Bart
Post by David Brown
What you have written here is all correct, but a more common method
void shout_a_number(int n) {
     printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each
has to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would evaluate
only one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct.
I don't think it's just opinion.
Yes, it is.

I don't disagree that such an "select one of these and evaluate only
that" construct can be a useful thing, or a perfectly good alternative
to to an "evaluate all of these then select one of them" construct. But
you are completely wrong to think that one of these two is somehow the
"true" or only correct way to have a selection.

In some languages, the construct for "A or B" will evaluate both, then
"or" them. In other languages, it will evaluate "A" then only evaluate
"B" if necessary. In others, expressions "A" and "B" cannot have
side-effects, so the evaluation or not makes no difference. All of
these are perfectly valid design choices for a language.
Post by Bart
In general, an if-else-if chain (which was the point of the OP), would
evaluate only one branch.
It evaluates all the conditionals down the chain until it hits a "true"
result, then evaluates the body of the "if" that matches, then skips the
rest.

(Of course generated code can evaluate all sorts of things in different
orders, as long as observable behaviour - side-effects - are correct.)
Post by Bart
So would a switch-case construct if sensibly
implemented (in C's version, anything goes).
C's switch is perfectly simply and clearly defined. It is not "anything
goes". The argument to the switch is evaluated once, then control jumps
to the label of the switch case, then evaluation continues from that
point. It is totally straight-forward.

You might not like the "fall-through" concept or the way C's switch does
not quite fit with structured programming. If so, I'd agree entirely.
The requirement for lots of "break;" statements in most C switch uses is
a source of countless errors in C coding and IMHO a clear mistake in the
language design. But that does not hinder C's switch statements from
being very useful, very easy to understand (when used sensibly), and
with no doubts about how they work (again, when used sensibly).
Post by Bart
The same applies to C's c?a:b operator: only one of a or b is evaluated,
not both.
You are conflating several ideas, then you wrote something that you
/know/ is pure FUD about C's switch statements. So writing "The same
applies" makes no sense.

You are, of course, correct that in "c ? a : b", "c" is evaluated first
and then one and only one of "a" and "b".
Post by Bart
(This also why implementing if, switch, ?: via functions, which lots are
keen to do in the reddit PL forum, requires closures, lazy evaluation or
other advanced features.)
Yes, you'd need something like that to implement such "short-circuit"
operators using functions in C. In other languages, things may be
different.
Post by Bart
Post by David Brown
  You are free to choose the rules you want for your own language, but
you are not free to dictate what you think the rules should be for
others.  (You are welcome to /opinions/, of course.)
Post by Bart
(In my implementations, a default/else branch value must be provided
if the whole thing is expected to return a value.)
OK, if that's what you want.  My preference, if I were putting
together what /I/ thought was an idea language for /my/ use, would be
heavy use of explicit specifications and contracts for code, so that a
default/else branch is either disallowed (if there the selection
covers all legal values) or required (if the selection is
abbreviated).  A default value "just in case" is, IMHO, worse than
useless.
All such multiway constructs in my languages (there are 4, one of which
the job of both 'if' and  C's ?:) have an optional else branch. A
missing 'else' has an notional 'void' type.
But it becomes mandatory if the whole thing returns a value, to satisfy
the type system, because otherwise it will try and match with 'void'.
Your language, your choice. I'd question the whole idea of having a
construct that can evaluate to something of different types in the first
place, whether or not it returns a value, but that's your choice.
Post by Bart
SOMETHING needs to happen when none of the branches are executed; what
value would be returned then? The behaviour needs to be defined. You
don't want to rely on compiler analysis for this stuff.
In my hypothetical language described above, it never happens that none
of the branches are executed.

Do you feel you need to write code like this?

const char * flag_to_text_A(bool b) {
if (b == true) {
return "It's true!";
} else if (b == false) {
return "It's false!";
} else {
return "Schrödinger's cat has escaped!";
}
}


When you have your "else" or "default" clause that is added for
something that can't ever happen, how do you test it?
Post by Bart
In C on the other hand, the ':' of '?:' is always needed, even when it
   p == NULL ? puts("error"): 0;
Given that the tertiary operator chooses between two things, it seems
fairly obvious that you need two alternatives to choose from - having a
choice operator without at least two choices would be rather useless.

I can't say I have ever seen the tertiary operator used like this.
There are a few C programmers that like to code with everything as
expressions, using commas instead of semicolons, but they are IMHO
mostly just being smart-arses. It's a lot more common to write :

if (!p) puts("error");

And in most cases, you'd have a return when "p" is not valid, or make
the rest of the function part of the conditional rather than continuing
with a null "p".
Post by Bart
Here, gcc at least, also requires the types of the two branches to
match, even though the whole construct yields no common value.
The C standards require a certain level of type matching here - it is
not gcc specific. (The exact requirements are in 6.5.15p3 of the C
standards - I am sure you read these when you implemented your own C
compiler.) And yes, the whole construct /does/ yield a value of a
common type. It is not the language's fault if some hypothetical
programmer writes something in an odd manner.
Post by Bart
Meanwhile
  (p = nil | print "error")
No else is needed.
In C you could write :

p == NULL || puts("error");

which is exactly the same structure.

I think all of these, including your construct in your language, are
smart-arse choices compared to a simple "if" statement, but personal
styles and preferences vary.
Bart
2024-11-02 20:44:31 UTC
Reply
Permalink
Post by David Brown
Post by Bart
Post by David Brown
Post by Bart
Post by David Brown
What you have written here is all correct, but a more common method
void shout_a_number(int n) {
     printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each
has to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would evaluate
only one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct.
I don't think it's just opinion.
Yes, it is.
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.

The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
Post by David Brown
I don't disagree that such an "select one of these and evaluate only
that" construct can be a useful thing, or a perfectly good alternative
to to an "evaluate all of these then select one of them" construct.  But
you are completely wrong to think that one of these two is somehow the
"true" or only correct way to have a selection.
In some languages, the construct for "A or B" will evaluate both, then
"or" them.  In other languages, it will evaluate "A" then only evaluate
"B" if necessary.  In others, expressions "A" and "B" cannot have
side-effects, so the evaluation or not makes no difference.  All of
these are perfectly valid design choices for a language.
Those logical operators that may or may not short-circuit.

One feature of my concept of 'multi-way select' is that there is one or
more controlling expressions which determine which path is followed.

So, I'd be interested in what you think of as a multi-way select which
may evaluate more than one branch. Or was it that 'or' example?
Post by David Brown
Post by Bart
In general, an if-else-if chain (which was the point of the OP), would
evaluate only one branch.
It evaluates all the conditionals down the chain until it hits a "true"
result, then evaluates the body of the "if" that matches, then skips the
rest.
I don't count evaluating the conditionals: here it is the branches that
count (since it is one of those that is 'selected' via those
conditionals), and here you admit that only one is executed.
Post by David Brown
(Of course generated code can evaluate all sorts of things in different
orders, as long as observable behaviour - side-effects - are correct.)
Post by Bart
So would a switch-case construct if sensibly implemented (in C's
version, anything goes).
C's switch is perfectly simply and clearly defined.  It is not "anything
goes".  The argument to the switch is evaluated once, then control jumps
to the label of the switch case, then evaluation continues from that
point.  It is totally straight-forward.
It's pretty much the complete opposite of straightforward, as you go on
to demonstrate.

C 'switch' looks like it might be properly structured if written
sensibly. The reality is different: what follows `switch (x)` is just
ONE C statement, often a compound statement.

Case labels can located ANYWHERE within that statement, including within
nested statements (eg. inside a for-statement), and including
'default:', which could go before all the case labels!

The only place they can't go is within a further nested switch, which
has its own set of case-labels.

Control tranfers to any matching case-label or 'default:' and just keeps
executing code within that ONE statement, unless it hits 'break;'.

It is totally chaotic. This is what I mean by 'anything goes'. This is a
valid switch statement for example: 'switch (x);'.

You can't use such a statement as a solid basis for a multi-way
construct that returns a value, since it is, in general, impossible to
sensibly enumerate the N branches.
Post by David Brown
You might not like the "fall-through" concept or the way C's switch does
not quite fit with structured programming.  If so, I'd agree entirely.
Good.
Post by David Brown
The requirement for lots of "break;" statements in most C switch uses is
a source of countless errors in C coding and IMHO a clear mistake in the
language design.  But that does not hinder C's switch statements from
being very useful, very easy to understand (when used sensibly), and
with no doubts about how they work (again, when used sensibly).
Post by Bart
The same applies to C's c?a:b operator: only one of a or b is
evaluated, not both.
You are conflating several ideas, then you wrote something that you
/know/ is pure FUD about C's switch statements.
It wasn't. YOU wrote FUD when you called them straightforward. I would
bet you that the majority of C programmers don't know just how weird
switch is.
Post by David Brown
So writing "The same
applies" makes no sense.
'The same applies' was in reference to this previous remark of mine:

"In general, an if-else-if chain (which was the point of the OP), would
evaluate only one branch. So would a switch-case construct if sensibly
implemented (in C's version, anything goes). "
Post by David Brown
You are, of course, correct that in "c ? a : b", "c" is evaluated first
and then one and only one of "a" and "b".
And here you confirm that it does in fact apply: only one branch is
executed.

You can't apply it to C's switch as there is no rigorous way of even
determining what is a branch. Maybe it is a span between 2 case labels?
But then, one of those might be in a different nested statement!
Post by David Brown
Post by Bart
(This also why implementing if, switch, ?: via functions, which lots
are keen to do in the reddit PL forum, requires closures, lazy
evaluation or other advanced features.)
Yes, you'd need something like that to implement such "short-circuit"
operators using functions in C.  In other languages, things may be
different.
Yes, short-circut operators would need the same features. That's why
it's easier to build this stuff into a core language than to try and
design a language where 90% of the features are there to implement what
should be core features.
Post by David Brown
Post by Bart
But it becomes mandatory if the whole thing returns a value, to
satisfy the type system, because otherwise it will try and match with
'void'.
Your language, your choice.
These things tend to come about because that is the natural order that
comes through. It's something I observed rather than decided.
Post by David Brown
  I'd question the whole idea of having a
construct that can evaluate to something of different types in the first
place, whether or not it returns a value, but that's your choice.
If the result of a multi-way execution doesn't yield a value to be used,
then the types don't matter.

If it does, then they DO matter, as they have to be compatible types in
a static language.

This is just common sense; I don't know why you're questioning it. (I'd
quite like to see a language of your design!)
Post by David Brown
Post by Bart
SOMETHING needs to happen when none of the branches are executed; what
value would be returned then? The behaviour needs to be defined. You
don't want to rely on compiler analysis for this stuff.
In my hypothetical language described above, it never happens that none
of the branches are executed.
Do you feel you need to write code like this?
const char * flag_to_text_A(bool b) {
    if (b == true) {
        return "It's true!";
    } else if (b == false) {
        return "It's false!";
    } else {
        return "Schrödinger's cat has escaped!";
    }
}
When you have your "else" or "default" clause that is added for
something that can't ever happen, how do you test it?
I write code like this:

func F(b) =
if X then
A # 'return' is optional
elsif Y then
B
fi
end

As it is, it requires 'else' (because this is a value-returning function.

X Y A B are arbitrary expressions. The need for 'else' is determined
during type analysis. Whether it will ever execute the default path
would be up to extra analysis, that I don't do, and would anyway be done
later.

You can't design a language like this where valid syntax depends on
compiler and what it might or might not discover when analysing the code.

The rule instead is simple: where a multi-path construct yields a value,
then it needs the default branch, always.

A compiler /might/ figure out it isn't needed, and not generate that bit
of code. (Or as I suggested, it might insert a suitable branch.)

You seem to like putting the onus on compiler writers to have to analyse
programs to the limit.

(Note that my example is for dynamic code; there X Y may only be known
at runtime anyway.)

In my languages, the last statement of a function can be arbitrarily
complex and nested; there could be dozens of points where a return value
is needed.
Post by David Brown
Post by Bart
In C on the other hand, the ':' of '?:' is always needed, even when it
    p == NULL ? puts("error"): 0;
Given that the tertiary operator chooses between two things, it seems
fairly obvious that you need two alternatives to choose from - having a
choice operator without at least two choices would be rather useless.
It seems you are just arguing in the defence of C rather than
objectively, and being contradictory in the process.

For example, earlier you said I'm wrong to insist on a default path for
multi-way ops when it is expected to yield a value. But here you say it
is 'obvious' for the ?: multi-way operator to insist on a default path
even when any value is not used.

This is on top of saying that I'm spreading 'FUD' about switch and that
is it really a perfectly straightforward feature!

Now *I* am wary of trusting your judgement.
Post by David Brown
I can't say I have ever seen the tertiary operator used like this. There
are a few C programmers that like to code with everything as
expressions, using commas instead of semicolons, but they are IMHO
    if (!p) puts("error");
Well, it happens, and I've seen it (and I've had to ensure my C compiler
deals with it when it comes up, which it has). Maybe some instances of
it are hidden behind macros.
Post by David Brown
Post by Bart
   (p = nil | print "error")
No else is needed.
    p == NULL || puts("error");
which is exactly the same structure.
This is new to me. So this is another possibility for the OP?

It's an untidy feature however; it's abusing || in similar ways to those
who separate things with commas to avoid needing a compounds statement.

It is also error prone as it is untuitive: you probably meant one of:

p != NULL || puts("error");
p == NULL && puts("error");

There are also limitations: what follows || or || needs to be something
that returns a type that can be coerced to an 'int' type.

(Note that the '|' is my example is not 'or'; it means 'then':

( c | a ) # these are exactly equivalent
if c then a fi

( c | a | ) # so are these
if c then a else b fi

There is no restriction on what a and b are, statements or expressions,
unless the whole returns some value.)
Post by David Brown
I think all of these, including your construct in your language, are
smart-arse choices compared to a simple "if" statement, but personal
styles and preferences vary.
C's if statement is rather limited. As it is only if-else, then
if-else-if sequences must be emulated using nested if-else-(if else (if
else....

Misleading indentation needs to be used to stop nested if's disappearing
to the right. When coding style mandates braces around if branches, an
exception needs to be made for if-else-if chains (otherwise you will end
up with }}}}}}}... at the end.

And the whole thing cannot return a value; a separate ?: feature (whose
branches must be expressions) is needed.

It is also liable to 'dangling' else, and error prone due to braces
being optional.

It's a mess. By contrast, my if statements look like this:

if then elsif then ... [else] fi


'elsif' is a part of the syntax. The whole thing can return a value.
There is a compact form (not for elsif, that would be too much) as shown
above.
Tim Rentsch
2024-11-02 22:08:38 UTC
Reply
Permalink
Post by David Brown
Post by Bart
Post by David Brown
Post by Bart
Post by David Brown
What you have written here is all correct, but a more common
void shout_a_number(int n) {
printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so
each has to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would
evaluate only one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct.
I don't think it's just opinion.
Yes, it is.
I believe the phrase "N-way-select" would be understood by
most people to mean either exactly one or at most one out
of the N choices is put into effect. Saying it's just an
opinion is idiotic. We may not know how different people
would understand it, but how they understand it is something
that can be determined objectively, simply by asking them.
Then we disagree on what 'multi-way' select might mean. I think it
means branching, even if notionally, on one-of-N possible code paths.
The whole construct may or may not return a value. If it does, then
one of the N paths must be a default path.
Alternatively there could be an implicit default value. For
example, a hypothetical construct

( p; q; r; s; t )

where all of the variables are pointers, might return the first
pointer than is non-null, or a null pointer if all of them are
null (with an obvious generalization if the result expressions
and gating boolean expressions are distinct). Isn't this how
'cond' in Lisp works? Return the first expression whose guard
is non-nil, or nil if all the guards are nil.
fir
2024-11-03 00:26:41 UTC
Reply
Permalink
...
as to this switch as i said the C jas some syntax that resembles switch
and it is

[2] { printf("one"), printf("two"), printf("three") }

i mean it is like this compound sometheng you posted

{ printf("one"), printf("two"), printf("three") } [2]

but with "key" on the left to ilustrate the analogy to

swich(n) {case 0: printf("one"); case 1: printf("two"); case 2:
rintf("three") }

imo the resemblance gives to think

the difference is this compound (array-like) example dont uses defined
keys so it semms some should be added

[n] {{1: printf("one")},{2: printf("two")},{3: printf("three")} }

so those deduction on switch gives the above imo

the question is if some things couldnt be ommitted for simplicity

[key] {'A': printf("one"); 'B': printf("two"); 'C': printf("three"}; }

something like that

(insted of

switch(key)
{
case 'A': printf("one"); break;
case 'B': printf("two"); break;
case 'C': printf("three"}; break;
}
Bart
2024-11-03 12:03:03 UTC
Reply
Permalink
Post by fir
 ...
as to this switch as i said the C jas some syntax that resembles switch
and it is
[2] { printf("one"), printf("two"), printf("three") }
i mean it is like this compound sometheng you posted
 { printf("one"), printf("two"), printf("three") } [2]
 but with "key" on the left to ilustrate the analogy to
rintf("three") }
imo the resemblance gives to think
the difference is this compound (array-like) example dont uses defined
keys so it semms some should be added
[n] {{1: printf("one")},{2: printf("two")},{3: printf("three")} }
so those deduction on switch gives the above imo
the question is if some things couldnt be ommitted for simplicity
[key] {'A': printf("one"); 'B': printf("two"); 'C': printf("three"}; }
something like that
(insted of
switch(key)
{
case 'A': printf("one"); break;
case 'B': printf("two"); break;
case 'C': printf("three"}; break;
}
Here the switch looks clearer. Write it with 300 cases instead of 3,
then that becomes obvious.

The first time I wrote a big C program, I used a syntax like this:

switch (x)
when 'A', 'B' then printf("one")
when 'C' then printf("two")
else printf("three")
endsw

This needed to be converted to normal C before compiling, but the macro
system wasn't quite up to the job (making using gnu C which allows for
lists of case labels).

Instead I used a script to do the conversion, which needed 1:1 line
correspondence. The result was something like this:

switch (x) {
break; case 'A': case 'B': printf("one");
break; case 'C': printf("two");
break; default: printf("three");
}
fir
2024-11-03 13:39:38 UTC
Reply
Permalink
Post by Bart
Post by fir
...
as to this switch as i said the C jas some syntax that resembles
switch and it is
[2] { printf("one"), printf("two"), printf("three") }
i mean it is like this compound sometheng you posted
{ printf("one"), printf("two"), printf("three") } [2]
but with "key" on the left to ilustrate the analogy to
rintf("three") }
imo the resemblance gives to think
the difference is this compound (array-like) example dont uses defined
keys so it semms some should be added
[n] {{1: printf("one")},{2: printf("two")},{3: printf("three")} }
so those deduction on switch gives the above imo
the question is if some things couldnt be ommitted for simplicity
[key] {'A': printf("one"); 'B': printf("two"); 'C': printf("three"}; }
something like that
(insted of
switch(key)
{
case 'A': printf("one"); break;
case 'B': printf("two"); break;
case 'C': printf("three"}; break;
}
Here the switch looks clearer. Write it with 300 cases instead of 3,
then that becomes obvious.
depend on what some understoods by clearer - imo not

this []{;;;} at least is like logically drawed from other c syntax

and switch case overally the word case is ok imo but the word switch is
overrally like wrong imo switch could be better replaced by two
word "select" and maybe "goto" as this swich that selects could use
select and this one wgo does goto could use word goto

goto key;
'A': printf("a");
'B': printf("b");
'C': printf("c");
'

overally thete is lso possibility to do it such way

void foo()
{

"a" { printf("aaa"); } //definitions not calls itself
"b" { printf("bbb"); }
"c" { printf("ccc"); }


"a";"b";"c"; //calls (???)
// would need maybe some some syntax to call it (many could be chosen)

// "a"() ? foo."a" ? foo.[key] ?

maybe this woudl be the best if established as ths is more syntaktc "low
lewel"

}
Post by Bart
switch (x)
when 'A', 'B' then printf("one")
when 'C' then printf("two")
else printf("three")
endsw
This needed to be converted to normal C before compiling, but the macro
system wasn't quite up to the job (making using gnu C which allows for
lists of case labels).
Instead I used a script to do the conversion, which needed 1:1 line
switch (x) {
break; case 'A': case 'B': printf("one");
break; case 'C': printf("two");
break; default: printf("three");
}
fir
2024-11-03 14:17:46 UTC
Reply
Permalink
Post by fir
Post by Bart
Post by fir
...
as to this switch as i said the C jas some syntax that resembles
switch and it is
[2] { printf("one"), printf("two"), printf("three") }
i mean it is like this compound sometheng you posted
{ printf("one"), printf("two"), printf("three") } [2]
but with "key" on the left to ilustrate the analogy to
rintf("three") }
imo the resemblance gives to think
the difference is this compound (array-like) example dont uses defined
keys so it semms some should be added
[n] {{1: printf("one")},{2: printf("two")},{3: printf("three")} }
so those deduction on switch gives the above imo
the question is if some things couldnt be ommitted for simplicity
[key] {'A': printf("one"); 'B': printf("two"); 'C': printf("three"}; }
something like that
(insted of
switch(key)
{
case 'A': printf("one"); break;
case 'B': printf("two"); break;
case 'C': printf("three"}; break;
}
Here the switch looks clearer. Write it with 300 cases instead of 3,
then that becomes obvious.
depend on what some understoods by clearer - imo not
this []{;;;} at least is like logically drawed from other c syntax
and switch case overally the word case is ok imo but the word switch is
overrally like wrong imo switch could be better replaced by two
word "select" and maybe "goto" as this swich that selects could use
select and this one wgo does goto could use word goto
goto key;
'A': printf("a");
'B': printf("b");
'C': printf("c");
'
overally thete is lso possibility to do it such way
void foo()
{
"a" { printf("aaa"); } //definitions not calls itself
"b" { printf("bbb"); }
"c" { printf("ccc"); }
"a";"b";"c"; //calls (???)
// would need maybe some some syntax to call it (many could be chosen)
// "a"() ? foo."a" ? foo.[key] ?
maybe this woudl be the best if established as ths is more syntaktc "low
lewel"
}
the calling sign shuld be needed as as for years i wanted to do
something like

void foo()
{
a {/**/}
b {/**/}
c {/**/}

a;b;b;c; //calls a calls b twice calls c

}

( you can alse make expressions of it a+b*c (where a,b,c are code blocks)

then if using

1 {/**/}
2 {/**/}
3 {/**/}

"A" {/**/}
"B" {/**/}
"CCC" {/**/}

it would clash with normal usage
lika printf("CCC");

so some operator to call it is needed (and it could wrk as swich if you
allow to call it on variable not immediate value
fir
2024-11-03 01:21:45 UTC
Reply
Permalink
Post by Bart
if then elsif then ... [else] fi
'elsif' is a part of the syntax. The whole thing can return a value.
There is a compact form (not for elsif, that would be too much) as shown
above.
as to if when thinking of it the if construct has such parts

if X then S else E

and the keyword if is not necessary imo as the expression x return
logical value them then can be used on this without if

X then {}
X else {}

i would prefer to denote (at least temporerely) then as ->
and else as ~> then you can build construct like


a -> b -> c -> d ~> e ~> f

when the arrows take logical value of the left
(if a true then b, if be true then c if c true then d,if
d false then e and if e false then f)

but some need also to use else to some previous espression and
i think how it could be done but maybe just parenthesis can be used

a (->b->c) ~>z

if a true then b and if b true then c but if a false then z
Bart
2024-11-03 11:47:41 UTC
Reply
Permalink
Post by fir
    if then elsif then ... [else] fi
'elsif' is a part of the syntax. The whole thing can return a value.
There is a compact form (not for elsif, that would be too much) as shown
above.
as to if when thinking of it the if construct has such parts
if X then S else E
and the keyword if is not necessary imo as the expression x return
logical value them then can be used on this without if
X then {}
X else {}
i would prefer to denote (at least temporerely) then as ->
and else as ~> then you can build construct like
a -> b -> c -> d ~> e ~> f
when the arrows take logical value of the left
(if a true then b, if be true then c if c true then d,if
d false then e and if e false then f)
but some need also to use else to some previous espression and
i think how it could be done but maybe just parenthesis can be used
a (->b->c) ~>z
if a true then b and if b true then c but if a false then z
C already has this (I've added parentheses for clarity):

(a ? (b ? c : -) : z)

This shows you haven't provided a branch for b being false.

Also it's not clear if you intended for b to be evaluated twice; I've
assumed only once as it is nonsense otherwise.
fir
2024-11-03 13:46:59 UTC
Reply
Permalink
Post by Bart
Post by fir
Post by Bart
if then elsif then ... [else] fi
'elsif' is a part of the syntax. The whole thing can return a value.
There is a compact form (not for elsif, that would be too much) as shown
above.
as to if when thinking of it the if construct has such parts
if X then S else E
and the keyword if is not necessary imo as the expression x return
logical value them then can be used on this without if
X then {}
X else {}
i would prefer to denote (at least temporerely) then as ->
and else as ~> then you can build construct like
a -> b -> c -> d ~> e ~> f
when the arrows take logical value of the left
(if a true then b, if be true then c if c true then d,if
d false then e and if e false then f)
but some need also to use else to some previous espression and
i think how it could be done but maybe just parenthesis can be used
a (->b->c) ~>z
if a true then b and if b true then c but if a false then z
(a ? (b ? c : -) : z)
This shows you haven't provided a branch for b being false.
coz ypu dont need to provide such branch
in c yu need to put that ":" each time? (if so some error was discovered
as it ould be better to not oblige its existence)
Post by Bart
Also it's not clear if you intended for b to be evaluated twice; I've
assumed only once as it is nonsense otherwise.
why twice? -> just goes forward on true and ~> goes forward on fales

a (->b ->c) ~>z

a is checked if true b is called if b is true c is called, if a was
false z is called (eveluated)
fir
2024-11-03 13:55:46 UTC
Reply
Permalink
Post by fir
Post by Bart
(a ? (b ? c : -) : z)
This shows you haven't provided a branch for b being false.
coz ypu dont need to provide such branch
in c yu need to put that ":" each time? (if so some error was discovered
as it ould be better to not oblige its existence)
ye i checked and it seems you cant

x ? printf("aa");

you must write

x ? printf("aa"):0;

this also go
x ?: printf("bbb");

thsi is a mistake as "?" should go and ":" optionally
David Brown
2024-11-03 17:00:51 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Bart
Post by David Brown
Post by Bart
Post by David Brown
What you have written here is all correct, but a more common
void shout_a_number(int n) {
     printf( (const char* []) { "ONE", "TWO", "THREE" } [n] );
}
That's more likely to match what people would want.
I was also trying to show that all elements are evaluated, so each
has to have some side-effect to illustrate that.
Fair enough.
Post by Bart
A true N-way-select construct (C only really has ?:) would evaluate
only one, and would deal with an out-of-range condition.
That's a matter of opinion and design choice, rather than being
requirements for a "true" select construct.
I don't think it's just opinion.
Yes, it is.
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
I would disagree on that definition, yes. A "multi-way selection" would
mean, to me, a selection of one of N possible things - nothing more than
that. It is far too general a phrase to say that it must involve
branching of some sort ("notional" or otherwise). And it is too general
to say if you are selecting one of many things to do, or doing many
things and selecting one.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
No, that is simply incorrect. For one thing, you can say that it is
perfectly fine for the selection construct to return a value sometimes
and not at other times. It's fine if it never returns at all for some
cases. It's fine to give selection choices for all possible inputs.
It's fine to say that the input must be a value for which there is a choice.

What I see here is that you don't like C's constructs (that may be for
good reasons, it may be from your many misunderstandings about C, or it
may be from your knee-jerk dislike of everything C related). You have
some different selection constructs in your own language, which you /do/
like. (It would be very strange for you to have constructs that you
don't like in your own personal one-man language.)

And then you are leaping from "I like things this way" to "Everyone
likes them this way", "This way is the only correct way", "This is the
true definition", and "Other ways are clearly wrong".
Post by Bart
Post by David Brown
I don't disagree that such an "select one of these and evaluate only
that" construct can be a useful thing, or a perfectly good alternative
to to an "evaluate all of these then select one of them" construct.
But you are completely wrong to think that one of these two is somehow
the "true" or only correct way to have a selection.
In some languages, the construct for "A or B" will evaluate both, then
"or" them.  In other languages, it will evaluate "A" then only
evaluate "B" if necessary.  In others, expressions "A" and "B" cannot
have side-effects, so the evaluation or not makes no difference.  All
of these are perfectly valid design choices for a language.
Those logical operators that may or may not short-circuit.
Usually a language definition will be explicit about whether or not they
short-circuit. Some may be vaguer, then it is only safe to use the
constructs when there are no side-effects involved. (Languages which do
not allow side-effects, such as pure functional programming languages,
may be intentionally vague.)
Post by Bart
One feature of my concept of 'multi-way select' is that there is one or
more controlling expressions which determine which path is followed.
Okay, that's fine for /your/ language's multi-way select construct. But
other people and other languages may do things differently.
Post by Bart
So, I'd be interested in what you think of as a multi-way select which
may evaluate more than one branch. Or was it that 'or' example?
The examples you started with in C - creating an array, then picking one
of them - is a perfectly good multi-way selection construct which
evaluates all the options and then picks the result of one of them.
Post by Bart
Post by David Brown
Post by Bart
In general, an if-else-if chain (which was the point of the OP),
would evaluate only one branch.
It evaluates all the conditionals down the chain until it hits a
"true" result, then evaluates the body of the "if" that matches, then
skips the rest.
I don't count evaluating the conditionals: here it is the branches that
count (since it is one of those that is 'selected' via those
conditionals), and here you admit that only one is executed.
That seems an arbitrary choice - the controlling expressions for the
conditionals also need to be evaluated.

And "admit" is a strange choice of words - are you trying to make it
sound like you have convinced me of something, or forced a concession of
some sort? I have merely being describing how C works here - this is a
matter of fact, not opinion or knowledge. We can all have opinions on
whether we like way C does things - and we may be persuaded to change
those opinions. But the reality of how C works is not up for debate.
Post by Bart
Post by David Brown
(Of course generated code can evaluate all sorts of things in
different orders, as long as observable behaviour - side-effects - are
correct.)
Post by Bart
So would a switch-case construct if sensibly implemented (in C's
version, anything goes).
C's switch is perfectly simply and clearly defined.  It is not
"anything goes".  The argument to the switch is evaluated once, then
control jumps to the label of the switch case, then evaluation
continues from that point.  It is totally straight-forward.
It's pretty much the complete opposite of straightforward, as you go on
to demonstrate.
I described it in a single sentence. Though I skipped some details, it
covered enough for almost all real-world uses of "switch".
Post by Bart
C 'switch' looks like it might be properly structured if written
sensibly. The reality is different: what follows `switch (x)` is just
ONE C statement, often a compound statement.
Grammatically it is one statement, but invariably that one statement is
a compound statement.
Post by Bart
Case labels can located ANYWHERE within that statement, including within
nested statements (eg. inside a for-statement), and including
'default:', which could go before all the case labels!
Yes - the programmer has a responsibility to write sensible code.

There are plenty of C programmers - including me - who would have
preferred to have "switch" be a more structured construct which could
not be intertwined with other constructs in this way. That does not
mean "switch" is not clearly defined - nor does it hinder almost every
real-world use of "switch" from being reasonably clear and structured.
It does, however, /allow/ people to use "switch" in more complex and
less clear ways.
Post by Bart
The only place they can't go is within a further nested switch, which
has its own set of case-labels.
Control tranfers to any matching case-label or 'default:' and just keeps
executing code within that ONE statement, unless it hits 'break;'.
It is totally chaotic. This is what I mean by 'anything goes'. This is a
valid switch statement for example: 'switch (x);'.
You are confusing "this makes it possible to write messy code" with a
belief that messy code is inevitable or required. And you are
forgetting that it is always possible to write messy or incomprehensible
code in any language, with any construct.
Post by Bart
You can't use such a statement as a solid basis for a multi-way
construct that returns a value, since it is, in general, impossible to
sensibly enumerate the N branches.
It is simple and obvious to enumerate the branches in almost all
real-world cases of switch statements. (And /please/ don't faff around
with cherry-picked examples you have found somewhere as if they were
representative of anything.)
Post by Bart
Post by David Brown
You might not like the "fall-through" concept or the way C's switch
does not quite fit with structured programming.  If so, I'd agree
entirely.
Good.
Post by David Brown
The requirement for lots of "break;" statements in most C switch uses
is a source of countless errors in C coding and IMHO a clear mistake
in the language design.  But that does not hinder C's switch
statements from being very useful, very easy to understand (when used
sensibly), and with no doubts about how they work (again, when used
sensibly).
Post by Bart
The same applies to C's c?a:b operator: only one of a or b is
evaluated, not both.
You are conflating several ideas, then you wrote something that you
/know/ is pure FUD about C's switch statements.
It wasn't.
Do you know what "FUD" means? "Fear, uncertainty and doubt". You
/know/ how switch statements work. You /know/ they are not hard to use,
or, in almost all cases, hard to understand. You have, so you claim,
implemented a C compiler - surely you understand the way things work
here. You know perfectly well that it is not "anything goes". Yet you
write what is, at best, wild exaggeration about how difficult and
incomprehensible it all is.
Post by Bart
YOU wrote FUD when you called them straightforward. I would
bet you that the majority of C programmers don't know just how weird
switch is.
I would bet you that the vast majority of C programmers are perfectly
capable of using "switch" without trouble (other than, perhaps,
forgetting a "break" statement or two - something that is easily spotted
by automated tools as used by most competent programmers). I would bet
you that they are also capable of understanding the vast majority of
real-world uses of "switch". I would bet the proportions here are not
much different from the use of, say, "for" loops in C.

I don't dispute that it is possible to do complicated things with
"switch" that can be hard to follow. Probably most C programmers would
have to think hard to understand how Duff's Device works, or some of the
macro and switch based libraries for making a poor man's coroutine
support for C.

But that's all like saying English is a difficult language because most
people don't understand the language of law court filings.
Post by Bart
Post by David Brown
So writing "The same applies" makes no sense.
"In general, an if-else-if chain (which was the point of the OP), would
evaluate only one branch. So would a switch-case construct if sensibly
implemented (in C's version, anything goes). "
Post by David Brown
You are, of course, correct that in "c ? a : b", "c" is evaluated
first and then one and only one of "a" and "b".
And here you confirm that it does in fact apply: only one branch is
executed.
So if I understand correctly, you are saying that chains of if/else, an
imaginary version of "switch", and the C tertiary operator all evaluate
the same things in the same way, while with C's switch you have no idea
what happens? That is true, if you cherry-pick what you choose to
ignore in each case until it fits your pre-conceived ideas.
Post by Bart
You can't apply it to C's switch as there is no rigorous way of even
determining what is a branch. Maybe it is a span between 2 case labels?
But then, one of those might be in a different nested statement!
Post by David Brown
Post by Bart
(This also why implementing if, switch, ?: via functions, which lots
are keen to do in the reddit PL forum, requires closures, lazy
evaluation or other advanced features.)
Yes, you'd need something like that to implement such "short-circuit"
operators using functions in C.  In other languages, things may be
different.
Yes, short-circut operators would need the same features. That's why
it's easier to build this stuff into a core language than to try and
design a language where 90% of the features are there to implement what
should be core features.
So does that mean you are happy that C has language features for
short-circuit evaluation in particular cases?
Post by Bart
Post by David Brown
Post by Bart
But it becomes mandatory if the whole thing returns a value, to
satisfy the type system, because otherwise it will try and match with
'void'.
Your language, your choice.
These things tend to come about because that is the natural order that
comes through. It's something I observed rather than decided.
No, what you call "natural" is entirely subjective. You have looked at
a microscopic fraction of code written in a tiny proportion of
programming languages within a very narrow set of programming fields.
That's not criticism - few people have looked at anything more. The
programming world is vast, and most of us have limited time for looking
at code. What I /do/ criticise is that your assumption that this almost
negligible experience gives you the right to decide what is "natural" or
"true", or how programming languages or tools "should" work. You need
to learn that other people have different ideas, needs, opinions or
preferences.
Post by Bart
Post by David Brown
  I'd question the whole idea of having a construct that can evaluate
to something of different types in the first place, whether or not it
returns a value, but that's your choice.
If the result of a multi-way execution doesn't yield a value to be used,
then the types don't matter.
Of course they do. If you have a typed language (not all programming
languages place significant store on the concept of "type"), then "no
value" has to fit into the type. Sometimes that might be done in an
obvious way within the type - a NULL pointer in a pointer type, or a NaN
in a floating point type. Maybe the type you use is a tagged union of
some kind - a "sum type" in programming language theory parlance. Maybe
it is something like C++'s std::optional<>, or a Haskell "Maybe" type.
It's still all types.
Post by Bart
If it does, then they DO matter, as they have to be compatible types in
a static language.
They have to be compatible in some way, according to the language rules.
Post by Bart
This is just common sense; I don't know why you're questioning it. (I'd
quite like to see a language of your design!)
def foo(n) :
if n == 1 : return 10
if n == 2 : return 20
if n == 3 : return

That's Python, quite happily having a multiple choice selection that
sometimes does not return a value. Yes, that is a dynamically typed
language, not a statically type language.

std::optional<int> foo(int n) {
if (n == 1) return 10;
if (n == 2) return 20;
if (n == 3) return {};
}

That's C++, a statically typed language, with a multiple choice
selection that sometimes does not return a value - the return type
supports values of type "int" and non-values.

const int * foo(int n) {
static const int ten = 10;
static const int twenty = 20;

if (n == 1) return &ten;
if (n == 2) return &twenty;
if (n == 3) return NULL;
}

In a language that I would design, I'd aim for something like the C++
version above but with core language support for sum types and optional
types, and type deduction for a case like this. (I'd also have other
differences in syntax that are not relevant here.)
Post by Bart
Post by David Brown
Post by Bart
SOMETHING needs to happen when none of the branches are executed;
what value would be returned then? The behaviour needs to be defined.
You don't want to rely on compiler analysis for this stuff.
In my hypothetical language described above, it never happens that
none of the branches are executed.
Do you feel you need to write code like this?
const char * flag_to_text_A(bool b) {
     if (b == true) {
         return "It's true!";
     } else if (b == false) {
         return "It's false!";
     } else {
         return "Schrödinger's cat has escaped!";
     }
}
When you have your "else" or "default" clause that is added for
something that can't ever happen, how do you test it?
   func F(b) =
      if X then
          A                # 'return' is optional
      elsif Y then
          B
      fi
   end
As it is, it requires 'else' (because this is a value-returning function.
X Y A B are arbitrary expressions. The need for 'else' is determined
during type analysis. Whether it will ever execute the default path
would be up to extra analysis, that I don't do, and would anyway be done
later.
But if it is not possible for neither of X or Y to be true, then how
would you test the "else" clause? Surely you are not proposing that
programmers be required to write lines of code that will never be
executed and cannot be tested?
Post by Bart
You can't design a language like this where valid syntax depends on
compiler and what it might or might not discover when analysing the code.
Why not? It is entirely reasonable to say that a compiler for a
language has to be able to do certain types of analysis. It is
certainly more reasonable to require the compiler to do extra work than
to require the programmer to do extra work. (Unless by "a language like
this", you mean "a language where the only user also implements the
compiler".)
Post by Bart
The rule instead is simple: where a multi-path construct yields a value,
then it needs the default branch, always.
A compiler /might/ figure out it isn't needed, and not generate that bit
of code. (Or as I suggested, it might insert a suitable branch.)
You seem to like putting the onus on compiler writers to have to analyse
programs to the limit.
I don't see that I have been putting much demand on the compiler here.
I am actually entirely happy with the concept of "undefined behaviour" -
if a function is specified to take an input in a certain range, and you
give it something outside that range, it is undefined behaviour. It is
just silly to force programmers to make up behaviour for something that
can't happen and has no meaning. (It is, however, very useful if the
tools can have a mode where they automatically add an "else" clause here
that exits with an error message - that kind of thing is helpful for
debugging.)

I would prefer a language to support explicit formal specifications for
inputs and outputs of functions, and that it did analysis (and had
debugging options) to help find errors in the code - but those are not
/required/ here.
Post by Bart
(Note that my example is for dynamic code; there X Y may only be known
at runtime anyway.)
In my languages, the last statement of a function can be arbitrarily
complex and nested; there could be dozens of points where a return value
is needed.
Post by David Brown
Post by Bart
In C on the other hand, the ':' of '?:' is always needed, even when
it is not expected to yield a value. Hence you often see this things
    p == NULL ? puts("error"): 0;
Given that the tertiary operator chooses between two things, it seems
fairly obvious that you need two alternatives to choose from - having
a choice operator without at least two choices would be rather useless.
It seems you are just arguing in the defence of C rather than
objectively, and being contradictory in the process.
For example, earlier you said I'm wrong to insist on a default path for
multi-way ops when it is expected to yield a value. But here you say it
is 'obvious' for the ?: multi-way operator to insist on a default path
even when any value is not used.
The "? :" tertiary operator in C is /not/ a multi-way operator - it is a
two-way choice. The clue is in the name often used - "tertiary" - which
means it has three operands. (To be fair, the C standards refer to it
as the "conditional operator", but they are very clear that it has three
operands.)
Post by Bart
This is on top of saying that I'm spreading 'FUD' about switch and that
is it really a perfectly straightforward feature!
Now *I* am wary of trusting your judgement.
Then RTFM to confirm what I write about C.
Post by Bart
Post by David Brown
I can't say I have ever seen the tertiary operator used like this.
There are a few C programmers that like to code with everything as
expressions, using commas instead of semicolons, but they are IMHO
     if (!p) puts("error");
Well, it happens, and I've seen it (and I've had to ensure my C compiler
deals with it when it comes up, which it has). Maybe some instances of
it are hidden behind macros.
And I've seen a three-legged dog. But generally, I'd say that dogs have
four legs.
Post by Bart
Post by David Brown
Post by Bart
   (p = nil | print "error")
No else is needed.
     p == NULL || puts("error");
which is exactly the same structure.
This is new to me. So this is another possibility for the OP?
Seriously? You might not have seen such a construct in real code (I
haven't, as far as I can recall), but it's not particularly hard to
imagine - especially not when you suggest exactly the same thing in your
language! The logic is the opposite of the original use of C's
conditional operator or the "if" version, but I assume it is the same
logic as your code.

(I believe in Perl it is common to write "do_something or die()" as a
structure.)
Post by Bart
It's an untidy feature however; it's abusing || in similar ways to those
who separate things with commas to avoid needing a compounds statement.
It's the same thing as in /your/ language, with the example /you/ gave!
I didn't suggest it or recommend it, I merely showed that what you
seemed to think is a marvellous unique feature of your special language
is ordinary C code. I don't know that I would describe it as "untidy",
and it is certainly not an "abuse" of the operator, but I would not feel
it is a particularly clear way to write the code in question, and I have
no intention of using it myself. (I'd use the "if" statement.)
Post by Bart
      p != NULL || puts("error");
      p == NULL && puts("error");
I assumed that in your language, "|" means "or", but that you have
simply chosen other rules for distinguishing bit-wise and logical "or".
So I copied that.

From what you write below, however, it seems that is not the case - you
are using the common "|" operator in a very uncommon manner that would
be unexpected and confusing to people with experience in any other
programming language.
Post by Bart
There are also limitations: what follows || or || needs to be something
that returns a type that can be coerced to an 'int' type.
Sure. In C, that's fine for most commonly used types. (Again, I don't
recommend the syntax.)
Post by Bart
   (  c |    a )          # these are exactly equivalent
   if c then a fi
   (  c |    a |    )     # so are these
   if c then a else b fi
There is no restriction on what a and b are, statements or expressions,
unless the whole returns some value.)
Ah, so your language has a disastrous choice of syntax here so that
sometimes "a | b" means "or", and sometimes it means "then" or
"implies", and sometimes it means "else". Why have a second syntax with
a confusing choice of operators when you have a perfectly good "if /
then / else" syntax? Or if you feel an operator adds a lot to the
language here, why not choose one that would make sense to people, such
as "=>" - the common mathematical symbol for "implies".
Post by Bart
Post by David Brown
I think all of these, including your construct in your language, are
smart-arse choices compared to a simple "if" statement, but personal
styles and preferences vary.
C's if statement is rather limited. As it is only if-else, then
if-else-if sequences must be emulated using nested if-else-(if else (if
else....
if (a) {
...
} else if (b) {
...
} else if (c) {
...
}

Do you think C programming would be revolutionised if there was an
"elseif" or "elif" keyword? Feel free to put :

#define elif else if

at the top of your code.

(Or use your own spelling, "elsif".)
Post by Bart
Misleading indentation needs to be used to stop nested if's disappearing
to the right. When coding style mandates braces around if branches, an
exception needs to be made for if-else-if chains (otherwise you will end
up with }}}}}}}... at the end.
This is not rocket science. I've known ten year olds that complain less
about how difficult it is to learn programming.
Post by Bart
And the whole thing cannot return a value; a separate ?: feature (whose
branches must be expressions) is needed.
It is also liable to 'dangling' else, and error prone due to braces
being optional.
   if then elsif then ... [else] fi
'elsif' is a part of the syntax. The whole thing can return a value.
There is a compact form (not for elsif, that would be too much) as shown
above.
There are a hundred and one different ways to make the syntax for
conditionals in a programming language. And there are dozens of choices
to be made regarding the distinction, or not, between statements and
expressions, and what can return or evaluate to values. There are pros
and cons of all of these, and supporters and detractors of them all.

Anyone who is convinced that their own personal preferences are more
"natural" or inherently superior to all other alternatives, and can't
justify their claims other than saying that everything else is "a mess",
is just navel-gazing.
Kaz Kylheku
2024-11-03 19:41:57 UTC
Reply
Permalink
Post by David Brown
like. (It would be very strange for you to have constructs that you
don't like in your own personal one-man language.)
It would, until you document it and other people use it. Then it can
happen that you regret some designs (for instance, because of the way
they clash with something shiny and new you would like to do)
but you are stuck, because you have to keep things working.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
Bart
2024-11-03 20:00:26 UTC
Reply
Permalink
I would disagree on that definition, yes.  A "multi-way selection" would
mean, to me, a selection of one of N possible things - nothing more than
that.  It is far too general a phrase to say that it must involve
branching of some sort ("notional" or otherwise).
Not really. If the possible options involving actions written in-line,
and you only want one of those executed, then you need to branch around
the others!
  And it is too general
to say if you are selecting one of many things to do, or doing many
things and selecting one.
Sorry, but this is the key part. You are not evaluating N things and
selecting one; you are evaluating ONLY one of N things.

The former is more like construction a list, the latter is multi-way
select. It's illustrated with this bit of scripting code:

x := ( printf("one"), printf("two"), printf("three"))[3]
println
println =x

y := (3 | printf("one"), printf("two"), printf("three") |
printf("default"))
println
println =y

The output is:

onetwothree
X= 5
three
Y= 5

For X, it builds a list by evaluating all the elements, and returns the
value of the last. For Y, it evaluates only ONE element (using internal
switch, so branching), which again is the last.

You don't seem keen on keeping these concepts distinct?

Well, C has EXACTLY those same concepts, and I can come up with similar
examples in C (I may even have posted some), for example using compound
literal for the first, and a regular if-else or nested ?: chain for the
second. (But only ?: can return that '5'.)
Post by Bart
The whole construct may or may not return a value. If it does, then
one of the N paths must be a default path.
No, that is simply incorrect.  For one thing, you can say that it is
perfectly fine for the selection construct to return a value sometimes
and not at other times.
How on earth is that going to satisfy the type system? You're saying
it's OK to have this:

int x = if (randomfloat()<0.5) 42;

Or even this, which was discussed recently, and which is apparently valid C:

int F(void) {
if (randomfloat()<0.5) return 42;

In the first example, you could claim that no assignment takes place
with a false condition (so x contains garbage). In the second example,
what value does F return when the condition is false?

You can't hide behind your vast hyper-optimising compiler; the language
needs to say something about it.

My language will not allow it. Most people would say that that's a good
thing. You seem to want to take the perverse view that such code should
be allowed to return garbage values or have undefined behaviour.

After all, this is C! But please tell me, what would be the downside of
not allowing it?

  It's fine if it never returns at all for some
cases.  It's fine to give selection choices for all possible inputs.
It's fine to say that the input must be a value for which there is a choice.
What I see here is that you don't like C's constructs (that may be for
good reasons, it may be from your many misunderstandings about C, or it
may be from your knee-jerk dislike of everything C related).
With justification. 0010 means 8 in C? Jesus.

It's hardly knee-jerk either since I first looked at it in 1982, when my
own language barely existed. My opinion has not improved.

(I remarked elsewhere that C is like the Trump of programming languages;
there is no amount of crassness that will turn people off it!)
  You have
some different selection constructs in your own language, which you /do/
like.  (It would be very strange for you to have constructs that you
don't like in your own personal one-man language.)
It's a one-man language but most of its constructs and features are
universal. And therefore can be used for comparison.
Post by Bart
One feature of my concept of 'multi-way select' is that there is one
or more controlling expressions which determine which path is followed.
Okay, that's fine for /your/ language's multi-way select construct.  But
other people and other languages may do things differently.
FGS, /how/ different? To select WHICH path or which element requires
some input. That's the controlling expression.

Or maybe with your ideal language, you can select an element of an array
without bothering to provide an index!
There are plenty of C programmers - including me - who would have
preferred to have "switch" be a more structured construct which could
not be intertwined with other constructs in this way.  That does not
mean "switch" is not clearly defined - nor does it hinder almost every
real-world use of "switch" from being reasonably clear and structured.
It does, however, /allow/ people to use "switch" in more complex and
less clear ways.
Try and write a program which takes any arbitrary switch construct (that
usually means written by someone else, because obviously all yours will
be sensible), and cleanly isolates all the branches including the
default branch.

Hint: the lack of 'break' in a non-empty span between two case labels
will blur the line. So will a conditional break (example below unless
it's been culled).
You are confusing "this makes it possible to write messy code" with a
belief that messy code is inevitable or required.  And you are
forgetting that it is always possible to write messy or incomprehensible
code in any language, with any construct.
I can't write that randomfloat example in my language. I can't leave out
a 'break' in a switch statement (it's not meaningful). It is impossible
to do the crazy things you can do with switch in C.

Yes, with most languages you can write nonsense programs, but that
doesn't give the language a licence to forget basic rules and common
sense, and just allow any old rubbish even if clearly wrong:

int F() {
F(1, 2.3, "four", F,F,F,F(),F(F()));
F(42);
}

This is apparently valid C. It is impossible to write this in my language.
Post by Bart
You can't use such a statement as a solid basis for a multi-way
construct that returns a value, since it is, in general, impossible to
sensibly enumerate the N branches.
It is simple and obvious to enumerate the branches in almost all
real-world cases of switch statements.  (And /please/ don't faff around
with cherry-picked examples you have found somewhere as if they were
representative of anything.)
Oh, right. I'm not allowed to use counter-examples to lend weight to my
comments. In that case, perhaps you shouldn't be allowed to use your
sensible examples either. After all we don't know what someone will feed
to a compiler.

But, suppose C was upgraded so that switch could return a value. For
that, you'd need the value at the end of each branch. OK, here's a
simple one:

y = switch (x) {
case 12:
if (c) case 14: break;
100;
case 13:
200;
break;
}

Any ideas? I will guess that x=12/c=false or c=13 will yield 200. What
avout x=12/c=true, or x=14, or x = anything else?
So if I understand correctly, you are saying that chains of if/else, an
imaginary version of "switch", and the C tertiary operator all evaluate
the same things in the same way, while with C's switch you have no idea
what happens?
Yes. With C's switch, you can't /in-general/ isolate things into
distinct blocks. You might have a stab if you stick to a subset of C and
follow various guidelines, in an effort to make 'switch' look normal.

See the example above.
  That is true, if you cherry-pick what you choose to
ignore in each case until it fits your pre-conceived ideas.
You're the one who's cherry-picking examples of C! Here is my attempt at
converting the above switch into my syntax (using a tool derived from my
C compiler):

switch x
when 12 then
if c then

fi
100
fallthrough
when 13 then
200
end switch

It doesn't attempt to deal with fallthrough, and misses out that
14-case, and that conditional break. It's not easy; I might have better
luck with assembly!
No, what you call "natural" is entirely subjective.  You have looked at
a microscopic fraction of code written in a tiny proportion of
programming languages within a very narrow set of programming fields.
I've worked with systems programming and have done A LOT in the 15 years
until the mid 90s. That included pretty much everything involved in
writing graphical applications given only a text-based disk OS that
provided file-handling.

Plus of course devising and implementing everthing needed to run my own
systems language. (After mid 90s, Windows took over half the work.)
That's not criticism - few people have looked at anything more.
Very few people use their own languages, especially over such a long
period, also use them to write commercial applications, or create
languages for others to use.
  What I /do/ criticise is that your assumption that this almost
negligible experience gives you the right to decide what is "natural" or
"true", or how programming languages or tools "should" work.
So, in your opinion, 'switch' should work how it works in C? That is the
most intuitive and natural way implementing it?
  You need
to learn that other people have different ideas, needs, opinions or
preferences.
Most people haven't got a clue about devising PLs.
Post by Bart
Post by David Brown
  I'd question the whole idea of having a construct that can evaluate
to something of different types in the first place, whether or not it
returns a value, but that's your choice.
If the result of a multi-way execution doesn't yield a value to be
used, then the types don't matter.
Of course they do.
Of course they don't! Here, F, G and H return int, float and void*
respectively:

if (c1) F();
else if (c2) G();
else H();

C will not complain that those branches yield different types. But you
say it should do? Why?

You're just being contradictory for the sake of it aren't you?!
Post by Bart
This is just common sense; I don't know why you're questioning it.
(I'd quite like to see a language of your design!)
    if n == 1 : return 10
    if n == 2 : return 20
    if n == 3 : return
That's Python, quite happily having a multiple choice selection that
sometimes does not return a value.
Python /always/ returns some value. If one isn't provided, it returns
None. Which means checking that a function returns an explicit value
goes out the window. Delete the 10 and 20 (or the entire body), and it
still 'works'.
  Yes, that is a dynamically typed
language, not a statically type language.
std::optional<int> foo(int n) {
    if (n == 1) return 10;
    if (n == 2) return 20;
    if (n == 3) return {};
}
That's C++, a statically typed language, with a multiple choice
selection that sometimes does not return a value - the return type
supports values of type "int" and non-values.
So what happens when n is 4? Does it return garbage (so that's bad).
Does it arrange to return some special value of 'optional' that means no
value? In that case, the type still does matter, but the language is
providing that default path for you.
Post by Bart
X Y A B are arbitrary expressions. The need for 'else' is determined
during type analysis. Whether it will ever execute the default path
would be up to extra analysis, that I don't do, and would anyway be
done later.
But if it is not possible for neither of X or Y to be true, then how
would you test the "else" clause?  Surely you are not proposing that
programmers be required to write lines of code that will never be
executed and cannot be tested?
Why not? They still have to write 'end', or do you propose that can be
left out if control never reaches the end of the function?!

(In earlier versions of my dynamic language, the compiler would insert
an 'else' branch if one was needed, returning 'void'.

I decided that requiring an explicit 'else' branch was better and more
failsafe.)
Post by Bart
You can't design a language like this where valid syntax depends on
compiler and what it might or might not discover when analysing the code.
Why not?  It is entirely reasonable to say that a compiler for a
language has to be able to do certain types of analysis.
This was the first part of your example:

const char * flag_to_text_A(bool b) {
if (b == true) {
return "It's true!";
} else if (b == false) {
return "It's false!";

/I/ would question why you'd want to make the second branch conditional
in the first place. Write an 'else' there, and the issue doesn't arise.

Because I can't see the point of deliberately writing code that usually
takes two paths, when either:

(1) you know that one will never be taken, or
(2) you're not sure, but don't make any provision in case it is

Fix that first rather relying on compiler writers to take care of your
badly written code.

And also, you keep belittling my abilities and my language, when C allows:

int F(void) {}

How about getting your house in order first.
Anyone who is convinced that their own personal preferences are more
"natural" or inherently superior to all other alternatives, and can't
justify their claims other than saying that everything else is "a mess",
is just navel-gazing.
I wrote more here but the post is already too long. Let's just that
'messy' is a fair assessment of C's conditional features, since you can
write this:

if (c1) {
if (c2) s1;
else if (c3)
s2;
}

What does it even mean?

The decisions *I* made resulted in a better syntax with fewer ways to
inadvertently get things wrong or writing ambiguous code. You for some
reason call that navel-gazing.
David Brown
2024-11-04 16:35:44 UTC
Reply
Permalink
Post by Bart
I would disagree on that definition, yes.  A "multi-way selection"
would mean, to me, a selection of one of N possible things - nothing
more than that.  It is far too general a phrase to say that it must
involve branching of some sort ("notional" or otherwise).
Not really. If the possible options involving actions written in-line,
and you only want one of those executed, then you need to branch around
the others!
And if it does /not/ involve actions "in-line", or if the semantics of
the selection say that all parts are evaluated before the selection,
then it would /not/ involve branching. I did not say that multi-way
selections cannot involve branching - I said that the phrase "multi-way
selection" is too vague to say that branches are necessary.
Post by Bart
  And it is too general to say if you are selecting one of many things
to do, or doing many things and selecting one.
Sorry, but this is the key part. You are not evaluating N things and
selecting one; you are evaluating ONLY one of N things.
I understand that this is key to what /you/ mean by "multi-way
selection". And if I thought that was what that phrase meant, then I'd
agree with you on many of your other points.

If you have some objective justification for insisting that the phrase
has a particular common meaning that rules out the possibility of first
creating N "things" and then selecting from them, then I would like to
hear about it. Until then, I will continue to treat it as a vague
phrase without a specific meaning, and repeating your assertions won't
change my mind.

To my mind, this is a type of "multi-way selection" :

(const int []){ a, b, c }[n];

I can't see any good reason to exclude it as fitting the descriptive
phrase. And if "a", "b" and "c" are not constant, but require
evaluation of some sort, it does not change things. Of course if these
required significant effort to evaluate, or had side-effects, then you
would most likely want a "multi-way selection" construction that did the
selection first, then the evaluation - but that's a matter of programmer
choice, and does not change the terms. (For some situations, such as
vector processing or SIMD work, doing the calculations before the
selection may be more time-efficient even if most of the results are
then discarded.)
Post by Bart
For X, it builds a list by evaluating all the elements, and returns the
value of the last. For Y, it evaluates only ONE element (using internal
switch, so branching), which again is the last.
You don't seem keen on keeping these concepts distinct?
I am very keen on keeping the concepts distinct in cases where it
matters. So they should be given distinct names or terms - or at least,
clear descriptive phrases should be used to distinguish them.

At the moment, you are saying that an "pet" is a four-legged creature
that purrs, and getting worked up when I some pets are dogs. It doesn't
matter how much of a cat person you are, there /are/ other kinds of pets.

It doesn't matter how keen you are on making the selection before the
evaluation, or how often it is the better choice, you can't impose
arbitrary restrictions on a general phrase.
Post by Bart
Post by Bart
The whole construct may or may not return a value. If it does, then
one of the N paths must be a default path.
No, that is simply incorrect.  For one thing, you can say that it is
perfectly fine for the selection construct to return a value sometimes
and not at other times.
How on earth is that going to satisfy the type system? You're saying
   int x = if (randomfloat()<0.5) 42;
In C, no. But when we have spread to other languages, including
hypothetical languages, there's nothing to stop that. Not only could it
be supported by the run-time type system, but it would be possible to
have compile-time types that are more flexible and only need to be
"solidified" during code generation. That might allow the language to
track things like "uninitialised" or "no value" during compilation
without having them part of a real type (such as std::optional<> or a C
struct with a "valid" field). All sorts of things are possible in a
programming language when you don't always think in terms of direct
translation from source to assembly.
Post by Bart
   int F(void) {
       if (randomfloat()<0.5) return 42;
Presumably you meant to add the closing } here ? Yes, that is valid C,
but it is undefined behaviour to use the value of F() if a value was not
returned.
Post by Bart
In the first example, you could claim that no assignment takes place
with a false condition (so x contains garbage). In the second example,
what value does F return when the condition is false?
It doesn't return a value. That is why it is UB to try to use that
non-existent value.
Post by Bart
You can't hide behind your vast hyper-optimising compiler; the language
needs to say something about it.
I am not suggesting any kind of "hyper-optimising" compiler. I am
suggesting that it is perfectly possible for a language to be defined in
a way that is different from your limited ideas (noting that your style
of language is not hugely different from C, at least in this aspect).
Post by Bart
My language will not allow it. Most people would say that that's a good
thing. You seem to want to take the perverse view that such code should
be allowed to return garbage values or have undefined behaviour.
Is your idea of "most people" based on a survey of more than one person?

Note that I have not suggested returning garbage values - I have
suggested that a language might support handling "no value" in a
convenient and safe manner. Many languages already do, though of course
it is debatable how safe, convenient or efficient the chosen solution
is. I've already given examples of std::optional<> in C++, Maybe types
in Haskell, null pointers in C, and you can add exceptions to that list
as a very different way of allowing functions to exit without returning
a value.

Totally independent of and orthogonal to that, I strongly believe that
there is no point in trying to define behaviour for something that
cannot happen, or for situations where there is no correct behaviour.
The principle of "garbage in, garbage out" was established by Babbage's
time, and the concept of functions that do not have defined values for
all inputs is as at least as old as the concept of mathematical function
- it goes back to the first person who realised you can't divide by
zero. The concept of UB is no more and no less than this.
Post by Bart
After all, this is C! But please tell me, what would be the downside of
not allowing it?
Are you asking what are the downsides of always requiring a returned
value of a specific type? Do you mean in addition to the things I have
already written?
Post by Bart
  It's fine if it never returns at all for some
cases.  It's fine to give selection choices for all possible inputs.
It's fine to say that the input must be a value for which there is a choice.
What I see here is that you don't like C's constructs (that may be for
good reasons, it may be from your many misunderstandings about C, or
it may be from your knee-jerk dislike of everything C related).
With justification. 0010 means 8 in C? Jesus.
I think the word "neighbour" is counter-intuitive to spell. Therefore
we should throw out the English language, because it is all terrible,
and it only still exists because some people insist on using it rather
than my own personal language of gobbledegook.

That's the knee-jerk "logic" you use in discussions about C. (Actually,
it's worse than that - you'd reject English because you think the word
"neighbour" is spelt with three "u's", or because you once saw it misspelt.)
Post by Bart
It's hardly knee-jerk either since I first looked at it in 1982, when my
own language barely existed. My opinion has not improved.
It's been knee-jerk all the time I have known you in this group.

Of course some of your criticisms of the language will be shared by
others - that's true of any language that has ever been used. And
different people will dislike different aspects of the language. But
you are unique in hating everything about C simply because it is in C.
Post by Bart
  You have some different selection constructs in your own language,
which you /do/ like.  (It would be very strange for you to have
constructs that you don't like in your own personal one-man language.)
It's a one-man language but most of its constructs and features are
universal. And therefore can be used for comparison.
Once a thread here has wandered this far off-topic, it is perhaps not
unreasonable to draw comparisons with your one-man language. But it is
not as useful as comparisons to real languages that other people might
be familiar with, or can at least read a little about.

The real problem with your language is that you think it is perfect, and
that everyone else should agree that it is perfect, and that any
language that does something differently is clearly wrong and inferior.
This hinders you from thinking outside the box you have build for yourself.
Post by Bart
Post by Bart
One feature of my concept of 'multi-way select' is that there is one
or more controlling expressions which determine which path is followed.
Okay, that's fine for /your/ language's multi-way select construct.
But other people and other languages may do things differently.
FGS, /how/ different? To select WHICH path or which element requires
some input. That's the controlling expression.
Have you been following this thread at all? Clearly a "multi-way
select" must have an input to choose the selection. But it does /not/
have to be a choice of a path for execution or evaluation.

When someone disagrees with a statement you made, please try to think a
little about which part of it they disagree with.
Post by Bart
Or maybe with your ideal language, you can select an element of an array
without bothering to provide an index!
There are plenty of C programmers - including me - who would have
preferred to have "switch" be a more structured construct which could
not be intertwined with other constructs in this way.  That does not
mean "switch" is not clearly defined - nor does it hinder almost every
real-world use of "switch" from being reasonably clear and structured.
It does, however, /allow/ people to use "switch" in more complex and
less clear ways.
Try and write a program which takes any arbitrary switch construct (that
usually means written by someone else, because obviously all yours will
be sensible), and cleanly isolates all the branches including the
default branch.
No. I am well aware that the flexibility of C's switch, and the
fall-through mechanism, make it more effort to parse and handle
algorithmically than if it were more structured. That has no bearing on
whether or not the meaning is clearly defined, or whether the majority
of real-world uses of "switch" are fairly easy to follow.
Post by Bart
Hint: the lack of 'break' in a non-empty span between two case labels
will blur the line. So will a conditional break (example below unless
it's been culled).
You are confusing "this makes it possible to write messy code" with a
belief that messy code is inevitable or required.  And you are
forgetting that it is always possible to write messy or
incomprehensible code in any language, with any construct.
I can't write that randomfloat example in my language.
Okay.
Post by Bart
I can't leave out
a 'break' in a switch statement (it's not meaningful). It is impossible
to do the crazy things you can do with switch in C.
Okay - although I can't see why you'd have a "break" statement here in
the first place.

As I've said many times, I'd prefer it if C's switches were more structured.

None of that has any bearing on other types of multi-way selection
constructs.
Post by Bart
Yes, with most languages you can write nonsense programs, but that
doesn't give the language a licence to forget basic rules and common
   int F() {
       F(1, 2.3, "four", F,F,F,F(),F(F()));
       F(42);
   }
This is apparently valid C. It is impossible to write this in my language.
It is undefined behaviour in C. Programmers are expected to write
sensible code.

I am confident that if I knew your language, I could write something
meaningless. But just as with C, doing so would be pointless.
Post by Bart
Post by Bart
You can't use such a statement as a solid basis for a multi-way
construct that returns a value, since it is, in general, impossible
to sensibly enumerate the N branches.
It is simple and obvious to enumerate the branches in almost all
real-world cases of switch statements.  (And /please/ don't faff
around with cherry-picked examples you have found somewhere as if they
were representative of anything.)
Oh, right. I'm not allowed to use counter-examples to lend weight to my
comments. In that case, perhaps you shouldn't be allowed to use your
sensible examples either. After all we don't know what someone will feed
to a compiler.
We /do/ know that most people would feed sensible code to compilers.
Post by Bart
But, suppose C was upgraded so that switch could return a value. For
that, you'd need the value at the end of each branch. OK, here's a
    y = switch (x) {
            if (c) case 14: break;
            100;
            200;
            break;
        }
Any ideas? I will guess that x=12/c=false or c=13 will yield 200. What
avout x=12/c=true, or x=14, or x = anything else?
What exactly is your point here? Am I supposed to be impressed that you
can add something to C and then write meaningless code with that extension?
Post by Bart
So if I understand correctly, you are saying that chains of if/else,
an imaginary version of "switch", and the C tertiary operator all
evaluate the same things in the same way, while with C's switch you
have no idea what happens?
Yes. With C's switch, you can't /in-general/ isolate things into
distinct blocks. You might have a stab if you stick to a subset of C and
follow various guidelines, in an effort to make 'switch' look normal.
See the example above.
You /can/ isolate things into distinct blocks, with occasional
fall-throughs, when you look at code people actually write. No one
writes code like your example above, so no one needs to be able to
interpret it.

Occasionally, people use "switch" statements in C for fancy things, like
coroutines. Then the logic flow can be harder to follow, but it is for
niche cases. People don't randomly mix switches with other structures.
Post by Bart
  That is true, if you cherry-pick what you choose to ignore in each
case until it fits your pre-conceived ideas.
You're the one who's cherry-picking examples of C!
I haven't even given any examples.
Post by Bart
Here is my attempt at
converting the above switch into my syntax (using a tool derived from my
    switch x
    when 12 then
        if c then
        fi
        100
        fallthrough
    when 13 then
        200
    end switch
It doesn't attempt to deal with fallthrough, and misses out that
14-case, and that conditional break. It's not easy; I might have better
luck with assembly!
No, what you call "natural" is entirely subjective.  You have looked
at a microscopic fraction of code written in a tiny proportion of
programming languages within a very narrow set of programming fields.
I've worked with systems programming and have done A LOT in the 15 years
until the mid 90s. That included pretty much everything involved in
writing graphical applications given only a text-based disk OS that
provided file-handling.
I know you have done a fair bit of programming. That does not change
what I said. (And I am not claiming that I have programmed in a wider
range of fields than you.)
Post by Bart
Plus of course devising and implementing everthing needed to run my own
systems language. (After mid 90s, Windows took over half the work.)
That's not criticism - few people have looked at anything more.
Very few people use their own languages, especially over such a long
period, also use them to write commercial applications, or create
languages for others to use.
When you use your own language, that inevitably /restricts/ your
experience with other programmers and other code. It is not a positive
thing in this context.
Post by Bart
  What I /do/ criticise is that your assumption that this almost
negligible experience gives you the right to decide what is "natural"
or "true", or how programming languages or tools "should" work.
So, in your opinion, 'switch' should work how it works in C? That is the
most intuitive and natural way implementing it?
No, I think there is quite a bit wrong with the way C's "switch"
statement works.

I don't think there is a single "most intuitive" or "most natural" way
to achieve a multi-way execution path selection statement in a language
- because "intuitive" and "natural" are highly subjective. There are
syntaxes, features and limitations that I think would be a reasonable
fit in C, but those could well be very different in other languages.
Post by Bart
  You need to learn that other people have different ideas, needs,
opinions or preferences.
Most people haven't got a clue about devising PLs.
I think you'd be surprised. Designing a general-purpose programming
language is not a small or easy task, and making a compiler is certainly
a big job. But you'd search far and wide to find an experienced
programmer who doesn't have opinions or ideas about languages and how
they might like to change them.
Post by Bart
Post by Bart
Post by David Brown
  I'd question the whole idea of having a construct that can
evaluate to something of different types in the first place, whether
or not it returns a value, but that's your choice.
If the result of a multi-way execution doesn't yield a value to be
used, then the types don't matter.
Of course they do.
Of course they don't! Here, F, G and H return int, float and void*
        if (c1) F();
   else if (c2) G();
   else         H();
C will not complain that those branches yield different types. But you
say it should do? Why?
Those branches don't yield different types in C. In C, branches don't
"yield" anything. Any results from calling these functions are, in
effect, cast to void.
Post by Bart
You're just being contradictory for the sake of it aren't you?!
No, but I think you are having great difficulty understanding what I
write. Maybe that's my fault as much as yours.
Post by Bart
Post by Bart
This is just common sense; I don't know why you're questioning it.
(I'd quite like to see a language of your design!)
     if n == 1 : return 10
     if n == 2 : return 20
     if n == 3 : return
That's Python, quite happily having a multiple choice selection that
sometimes does not return a value.
Python /always/ returns some value. If one isn't provided, it returns
None. Which means checking that a function returns an explicit value
goes out the window. Delete the 10 and 20 (or the entire body), and it
still 'works'.
"None" is the Python equivalent of "no value".

Maybe you are thinking about returning an unspecified value of a type
such as "int", rather than returning no value?
Post by Bart
  Yes, that is a dynamically typed language, not a statically type
language.
std::optional<int> foo(int n) {
     if (n == 1) return 10;
     if (n == 2) return 20;
     if (n == 3) return {};
}
That's C++, a statically typed language, with a multiple choice
selection that sometimes does not return a value - the return type
supports values of type "int" and non-values.
So what happens when n is 4? Does it return garbage (so that's bad).
It is undefined behaviour, as you would expect. (In my hypothetical
language that had better handling for "no value", falling off the end of
the function would return "no value" - in C++, that's std::nullopt,
which is what you get with "return {};" here.)
Post by Bart
Does it arrange to return some special value of 'optional' that means no
value?
No. C++ rules for function returns are similar to C's, but a little
stricter - you are not allowed to fall off the end of a non-void
function (excluding main(), constructors, destructors and coroutines).
If you break the rules, there is no defined behaviour.

The "return {};" returns the special "std::nullopt;" value (converted to
the actual std::optional<T> type) that means "no value".

Roughly speaking, a C++ std::optional<T> is like a C struct:

struct {
bool valid;
T value;
}
Post by Bart
In that case, the type still does matter, but the language is
providing that default path for you.
Post by Bart
X Y A B are arbitrary expressions. The need for 'else' is determined
during type analysis. Whether it will ever execute the default path
would be up to extra analysis, that I don't do, and would anyway be
done later.
But if it is not possible for neither of X or Y to be true, then how
would you test the "else" clause?  Surely you are not proposing that
programmers be required to write lines of code that will never be
executed and cannot be tested?
Why not? They still have to write 'end', or do you propose that can be
left out if control never reaches the end of the function?!
I'm guessing that "end" here is part of the syntax of your function
definitions in your language. That's not executable code, but part of
the syntax.
Post by Bart
(In earlier versions of my dynamic language, the compiler would insert
an 'else' branch if one was needed, returning 'void'.
I decided that requiring an explicit 'else' branch was better and more
failsafe.)
Post by Bart
You can't design a language like this where valid syntax depends on
compiler and what it might or might not discover when analysing the code.
Why not?  It is entirely reasonable to say that a compiler for a
language has to be able to do certain types of analysis.
 const char * flag_to_text_A(bool b) {
    if (b == true) {
        return "It's true!";
    } else if (b == false) {
        return "It's false!";
/I/ would question why you'd want to make the second branch conditional
in the first place. Write an 'else' there, and the issue doesn't arise.
Perhaps I want to put it there for symmetry.
Post by Bart
Because I can't see the point of deliberately writing code that usually
 (1) you know that one will never be taken, or
 (2) you're not sure, but don't make any provision in case it is
Fix that first rather relying on compiler writers to take care of your
badly written code.
I am not expecting anything from compiler writers here. I am asking
/you/ why you want to force /programmers/ to write extra code that they
know is useless.
Post by Bart
  int F(void) {}
How about getting your house in order first.
If I were the designer of the C language and the maintainer of the C
standards, you might have a point. C is not /my/ language.
Post by Bart
Anyone who is convinced that their own personal preferences are more
"natural" or inherently superior to all other alternatives, and can't
justify their claims other than saying that everything else is "a
mess", is just navel-gazing.
I wrote more here but the post is already too long.
Ah, a point that we can agree on 100% :-)
Post by Bart
Let's just that
'messy' is a fair assessment of C's conditional features, since you can
No, let's not just say that.

We can agree that C /lets/ people write messy code. It does not
/require/ it. And I have never found a programming language that stops
people writing messy code.
Bart
2024-11-04 19:50:40 UTC
Reply
Permalink
    (const int []){ a, b, c }[n];
I can't see any good reason to exclude it as fitting the descriptive
phrase.
And if "a", "b" and "c" are not constant, but require
evaluation of some sort, it does not change things.  Of course if these
required significant effort to evaluate,
Or you had a hundred of them.
or had side-effects, then you
would most likely want a "multi-way selection" construction that did the
selection first, then the evaluation - but that's a matter of programmer
choice, and does not change the terms.
You still don't get how different the concepts are. Everybody is
familiar with N-way selection when it involves actions, eg. statements.
Because they will be in form of a switch statement, or an if-else chain.

They will expect one branch only to evaluated. Otherwise, there's no
point in a selection or condition, if all will be evaluated anyway!

But I think they are less familiar with the concept when it mainly
involves expressions, and the whole thing returns a value.

The only such things in C are the ?: operator, and those compound
literals. And even then, those do not allow arbitrary statements.

Here is a summary of C vs my language.

In C, 0 or 1 branches will be evaluated (except for ?: where it is
always 1.)

In M, 0 or 1 branches are evaluated, unless it yields a value or lvalue,
then it must be 1 (and those need an 'else' branch):

C M

if-else branches can be exprs/stmts Y Y
if-else can yield a value N Y
if-else can be an lvalue N Y

?: branches can be exprs/stmts Y Y (M's is a form of if)
?: can yield a value Y Y
?: can be an lvalue N Y (Only possible in C++)

switch branches can have expr/stmts Y Y
switch can yield a value N Y
switch can be an lvalue N Y

select can have exprs/stmts - Y (Does not exist in C)
select can yield a value - Y
select can be an lvalue - Y

case-select has exprs/stmts - Y
case-select can yield a value - Y
case-select can be an lvalue - Y

15 Ys in the M column, vs 4 Ys in the C column, with only 1 for
value-returning. You can see why C users might be less familiar with the
concepts.
I am very keen on keeping the concepts distinct in cases where it
matters.
I know, you like to mix things up. I like clear lines:

func F:int ... Always returns a value
proc P ... Never returns a value
    int x = if (randomfloat()<0.5) 42;
In C, no.  But when we have spread to other languages, including
hypothetical languages, there's nothing to stop that.  Not only could it
be supported by the run-time type system, but it would be possible to
have compile-time types that are more flexible
This is a program from my 1990s scripting language which was part of my
CAD application:

n := 999
x := (n | 10, 20, 30)
println x

This uses N-way select (and evaluating only one option!). But there is
no default path (it is added by the bytecode compiler).

The output, since n is out of range, is this:

<Void>

In most arithmetic, using a void value is an error, so it's likely to
soon go wrong. I now require a default branch, as that is safer.
and only need to be
"solidified" during code generation.  That might allow the language to
track things like "uninitialised" or "no value" during compilation
without having them part of a real type (such as std::optional<> or a C
But you are always returning an actual type in agreement with the
language. That is my point. You're not choosing to just fall off that
cliff and return garbage or just crash.

However, your example with std::optional did just that, despite having
that type available.
It doesn't return a value.  That is why it is UB to try to use that
non-existent value.
And why it is so easy to avoid that UB.
My language will not allow it. Most people would say that that's a
good thing. You seem to want to take the perverse view that such code
should be allowed to return garbage values or have undefined behaviour.
Is your idea of "most people" based on a survey of more than one person?
So, you're suggesting that "most people" would prefer a language that
lets you do crazy, unsafe things for no good reason? That is, unless you
prefer to fall off that cliff I keep talking about.

The fact is, I spend a lot of time implementing this stuff, but I
wouldn't even know how to express some of the odd things in C. My
current C compiler uses a stack-based IL. Given this:

#include <stdio.h>

int F(void){}

int main(void) {
int a;
a=F();
printf("%d\n", a);
}

It just about works when generating native code (I'm not quite sure
how); but it just returns whatever garbage is in the register:

c:\cxp>cc -run t # here runs t.c as native code in memory
1900545

But the new compiler can also directly interpret that stack IL:

c:\cxp>cc -runp t
PC Exec error: RETF/SP mismatch: old=3 curr=2 seqno: 7

The problem is that the call-function handling expects a return value to
have been pushed. But nothing has been pushed in this case. And the
language doesn't allow me to detect that.

(My compiler could detect some cases, but not all, and even it it could,
it would report false positives of a missing return, for functions that
did always return early.)

So this is a discontinuity in the language, a schism, an exception that
shouldn't be there. It's unnatural. It looked off to me, and it is off
in practice, so it's not just an opinion.

To fix this would require my always pushing some dummy value at the
closing } of the function, if the operand stack is empty at that point.

Which is sort of what you are claiming you don't want to impose on the
programmer. But it looks like it's needed anyway, otherwise the function
is out of kilter.
Note that I have not suggested returning garbage values - I have
suggested that a language might support handling "no value" in a
convenient and safe manner.
But in C it is garbage. And I've show an example of my language handling
'no value' in a scheme from the 1990s; I decided to require an explicit
'else' branch, which you seem to think is some kind of imposition.

Well, it won't kill you, and it can make programs more failsafe. It is
also friendly to compilers that aren't 100MB monsters.
Totally independent of and orthogonal to that, I strongly believe that
there is no point in trying to define behaviour for something that
cannot happen,
But it could for n==4.
With justification. 0010 means 8 in C? Jesus.
I think the word "neighbour" is counter-intuitive to spell.
EVERYBODY agrees that leading zero octals in C were a terrible idea. You
can't say it's just me thinks that!
Once a thread here has wandered this far off-topic, it is perhaps not
unreasonable to draw comparisons with your one-man language.
Suppose I'd made my own hammer. The things I'd use it for are not going
to that different: hammering in nails, pulling them out, or generally
bashing things about.

As I said, the things my language does are universal. The way it does
them are better thought out and tidier.
The real problem with your language is that you think it is perfect
Compared with C, it a huge improvement. Compared with most other modern
languages, 95% of what people expect now is missing.
    int F() {
        F(1, 2.3, "four", F,F,F,F(),F(F()));
        F(42);
It is undefined behaviour in C.  Programmers are expected to write
sensible code.
But it would be nice if the language stopped people writing such things,
yes?

Can you tell me which other current languages, other than C++ and
assembly, allow such nonsense?

None? So it's not just me and my language then! Mine is lower level and
still plenty unsafe, but it has somewhat higher standards.
If I were the designer of the C language and the maintainer of the C
standards, you might have a point.  C is not /my/ language.
You do like to defend it though.
We can agree that C /lets/ people write messy code.  It does not
/require/ it.  And I have never found a programming language that stops
people writing messy code.
I had included half a dozen points that made C's 'if' error prone and
confusing, that would not occur in my syntax because it is better designed.

You seem to be incapable of drawing a line beween what a language can
enforce, and what a programmer is free to express.

Or rather, because a programmer has so much freedom anyway, let's not
bother with any lines at all! Just have a language that simply doesn't care.
David Brown
2024-11-04 21:48:12 UTC
Reply
Permalink
Post by Bart
     (const int []){ a, b, c }[n];
I can't see any good reason to exclude it as fitting the descriptive
phrase.
And if "a", "b" and "c" are not constant, but require evaluation of
some sort, it does not change things.  Of course if these required
significant effort to evaluate,
Or you had a hundred of them.
or had side-effects, then you would most likely want a "multi-way
selection" construction that did the selection first, then the
evaluation - but that's a matter of programmer choice, and does not
change the terms.
You still don't get how different the concepts are.
Yes, I do. I also understand how they are sometimes exactly the same
thing, depending on the language, and how they can often have the same
end result, depending on the details, and how they can often be
different, especially in the face of side-effects or efficiency concerns.

Look, it's really /very/ simple.

A) You can have a construct that says "choose one of these N things to
execute and evaluate, and return that value (if any)".

B) You can have a construct that says "here are N things, select one of
them to return as a value".

Both of these can reasonably be called "multi-way selection" constructs.
Some languages can have one as a common construct, other languages may
have the other, and many support both in some way. Pretty much any
language that allows the programmer to have control over execution order
will let you do both in some way, even if there is not a clear language
construct for it and you have to write it manually in code.

Mostly type A will be most efficient if there is a lot of effort
involved in putting together the things to select. Type B is likely to
be most efficient if you already have the collection of things to choose
from (it can be as simple as an array lookup), if the creation of the
collection can be done in parallel (such as in some SIMD uses), or if
the cpu can generate them all before it has established the selection index.

Sometimes type A will be the simplest and clearest in the code,
sometimes type B will be the simplest and clearest in the code.

Both of these constructs are "multi-way selections".


Your mistake is in thinking that type A is all there is and all that
matters, possibly because you feel you have a better implementation for
it than C has. (I think that you /do/ have a nicer switch than C, but
that does not justify limiting your thinking to it.)
Bart
2024-11-05 02:11:46 UTC
Reply
Permalink
Post by Bart
     (const int []){ a, b, c }[n];
I can't see any good reason to exclude it as fitting the descriptive
phrase.
And if "a", "b" and "c" are not constant, but require evaluation of
some sort, it does not change things.  Of course if these required
significant effort to evaluate,
Or you had a hundred of them.
or had side-effects, then you would most likely want a "multi-way
selection" construction that did the selection first, then the
evaluation - but that's a matter of programmer choice, and does not
change the terms.
You still don't get how different the concepts are.
Yes, I do.  I also understand how they are sometimes exactly the same
thing, depending on the language, and how they can often have the same
end result, depending on the details, and how they can often be
different, especially in the face of side-effects or efficiency concerns.
Look, it's really /very/ simple.
A) You can have a construct that says "choose one of these N things to
execute and evaluate, and return that value (if any)".
B) You can have a construct that says "here are N things, select one of
them to return as a value".
Both of these can reasonably be called "multi-way selection" constructs.
 Some languages can have one as a common construct, other languages may
have the other, and many support both in some way.  Pretty much any
language that allows the programmer to have control over execution order
will let you do both in some way, even if there is not a clear language
construct for it and you have to write it manually in code.
Mostly type A will be most efficient if there is a lot of effort
involved in putting together the things to select.  Type B is likely to
be most efficient if you already have the collection of things to choose
from (it can be as simple as an array lookup), if the creation of the
collection can be done in parallel (such as in some SIMD uses), or if
the cpu can generate them all before it has established the selection index.
Sometimes type A will be the simplest and clearest in the code,
sometimes type B will be the simplest and clearest in the code.
Both of these constructs are "multi-way selections".
Your mistake is in thinking that type A is all there is and all that
matters, possibly because you feel you have a better implementation for
it than C has.  (I think that you /do/ have a nicer switch than C, but
that does not justify limiting your thinking to it.)
You STILL don't get it. Suppose this wasn't about returning a value, but
executing one piece of code from a conditional set of statements.

In C that might be using an if/else chain, or switch. Other languages
might use a match statement.

Universally only one of those pieces of code will be evaluated. Unless
you can point me to a language where, in IF C THEN A ELSE B, *both* A
and B statements are executed.

Do you agree so far? If so call that Class I.

Do you also agree that languages have data stuctures, and those often
have constructors that will build a data structure element by element?
So all elements necessarily have to be evaluated. (Put aside selecting
one for now; that is a separate matter).

Call that Class II.

What my languages do, is that ALL the constructs in Class I that are
commonly used to execute one of N branches, can also return values.
(Which can require each branch to yield a type compatible with all the
others; another separate matter.)

Do you now see why it is senseless for my 'multi-way' selections to work
any other way. It would mean that:

x := if C then A else B fi

really could both evaluate A and B whatever the value of C! Whatever
that IF construct does here, has to do the same even without that 'x :='
a the start.

Of course, I support the sorts of indexing, of an existing or
just-created data structure, that belong in Class II.

Although it would not be particularly efficient to do this:

(f1(), f2(), .... f100())[100] # (1-based)

Since you will execute 100 functions rather than just one. But perhaps
there is a good reason for it. In that is needed, then the construct exists.

Another diference between Class I (when used to yield values) and Class
II, is that an out-of-bounds selector in Part II either yields a runtime
error (or raises an exception), or may just go wrong in my lower-level
language.

But in Class I, the selector is either range-checked or falls off the
end of a test sequence, and a default value is provided.
David Brown
2024-11-04 22:25:32 UTC
Reply
Permalink
Post by Bart
Here is a summary of C vs my language.
<snip the irrelevant stuff>
Post by Bart
Post by David Brown
I am very keen on keeping the concepts distinct in cases where it
matters.
  func F:int ...              Always returns a value
  proc P  ...                 Never returns a value
Oh, you /know/ that, do you? And how do you "know" that? Is that
because you still think I am personally responsible for the C language,
and that I think C is the be-all and end-all of perfect languages?

I agree that it can make sense to divide different types of "function".
I disagree that whether or not a value is returned has any significant
relevance. I see no difference, other than minor syntactic issues,
between "int foo(...)" and "void foo(int * result, ...)".

A much more useful distinction would be between Malcolm-functions and
Malcolm-procedures. "Malcolm-functions" are "__attribute__((const))" in
gcc terms or "[[unsequenced]]" in C23 terms (don't blame me for the
names here). In other words, they have no side-effects and their
result(s) are based entirely on their inputs. "Malcolm-procedures" can
have side-effects and interact with external data. I would possibly add
to that "meta-functions" that deal with compile-time information -
reflection, types, functions, etc.
Post by Bart
Post by David Brown
and only need to be "solidified" during code generation.  That might
allow the language to track things like "uninitialised" or "no value"
during compilation without having them part of a real type (such as
std::optional<> or a C
But you are always returning an actual type in agreement with the
language. That is my point. You're not choosing to just fall off that
cliff and return garbage or just crash.
However, your example with std::optional did just that, despite having
that type available.
Post by David Brown
It doesn't return a value.  That is why it is UB to try to use that
non-existent value.
And why it is so easy to avoid that UB.
I agree. I think C gets this wrong. That's why I, and pretty much all
other C programmers, use a subset of C that disallows falling off the
end of a function with a non-void return type. Thus we avoid that UB.

(The only reason it is acceptable syntax in C, AFAIK, is because early
versions of C had "default int" everywhere - there were no "void"
functions.)
Post by Bart
Post by David Brown
Note that I have not suggested returning garbage values - I have
suggested that a language might support handling "no value" in a
convenient and safe manner.
But in C it is garbage.
Note that /I/ have not suggested returning garbage values.

I have not said that I think C is defined in a good way here. You are,
as so often, mixing up what people say they like with what C does (or
what you /think/ C does, as you are often wrong). And as usual you mix
up people telling you what C does with what people think is a good idea
in a language.
Post by Bart
Post by David Brown
Totally independent of and orthogonal to that, I strongly believe that
there is no point in trying to define behaviour for something that
cannot happen,
But it could for n==4.
Again, you /completely/ miss the point.

If you have a function (or construct) that returns a correct value for
inputs 1, 2 and 3, and you never pass it the value 4 (or anything else),
then there is no undefined behaviour no matter what the code looks like
for values other than 1, 2 and 3. If someone calls that function with
input 4, then /their/ code has the error - not the code that doesn't
handle an input 4.
Post by Bart
EVERYBODY agrees that leading zero octals in C were a terrible idea. You
can't say it's just me thinks that!
I agree that this a terrible idea.
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60523>

But picking one terrible idea in C does not mean /everything/ in C is a
terrible idea! /That/ is what you got wrong, as you do so often.
Post by Bart
Post by David Brown
    int F() {
        F(1, 2.3, "four", F,F,F,F(),F(F()));
        F(42);
It is undefined behaviour in C.  Programmers are expected to write
sensible code.
But it would be nice if the language stopped people writing such things,
yes?
Sure. That's why sane programmers use decent tools - the language might
not stop them writing this, but the tools do.
Post by Bart
Can you tell me which other current languages, other than C++ and
assembly, allow such nonsense?
Python.

Of course, it is equally meaningless in Python as it is in C.
Post by Bart
None? So it's not just me and my language then! Mine is lower level and
still plenty unsafe, but it has somewhat higher standards.
Post by David Brown
If I were the designer of the C language and the maintainer of the C
standards, you might have a point.  C is not /my/ language.
You do like to defend it though.
I defend it if that is appropriate. Mostly, I /explain/ it to you. It
is bizarre that people need to do that for someone who claims to have
written a C compiler, but there it is.
Post by Bart
Post by David Brown
We can agree that C /lets/ people write messy code.  It does not
/require/ it.  And I have never found a programming language that
stops people writing messy code.
I had included half a dozen points that made C's 'if' error prone and
confusing, that would not occur in my syntax because it is better designed.
I'm glad you didn't - it would be a waste of effort.
Post by Bart
You seem to be incapable of drawing a line beween what a language can
enforce, and what a programmer is free to express.
I can't see how you could reach that conclusion.
Post by Bart
Or rather, because a programmer has so much freedom anyway, let's not
bother with any lines at all! Just have a language that simply doesn't care.
You /do/ understand that I use top-quality tools with carefully chosen
warnings, set to throw fatal errors, precisely because I want a language
that has a lot more "lines" and restrictions that your little tools?
/Every/ C programmer uses a restricted subset of C - some more
restricted than others. I choose to use a very strict subset of C for
my work, because it is the best language for the tasks I need to do. (I
also use a very strict subset of C++ when it is a better choice.)
Bart
2024-11-04 23:44:34 UTC
Reply
Permalink
Post by David Brown
Post by Bart
But it could for n==4.
Again, you /completely/ miss the point.
If you have a function (or construct) that returns a correct value for
inputs 1, 2 and 3, and you never pass it the value 4 (or anything else),
then there is no undefined behaviour no matter what the code looks like
for values other than 1, 2 and 3.  If someone calls that function with
input 4, then /their/ code has the error - not the code that doesn't
handle an input 4.
This is the wrong kind of thinking.

If this was a library function then, sure, you can stipulate a set of
input values, but that's at a different level, where you are writing
code on top of a working, well-specified language.

You don't make use of holes in the language, one that can cause a crash.
That is, by allowing a function to run into an internal RET op with no
provision for a result. That's if there even is a RET; perhaps your
compilers are so confident that that path is not taken, or you hint it
won't be, that they won't bother!

It will start executing whatever random bytes follow the function.

As I said in my last post, a missing return value caused an internal
error in one of my C implementations because a pushed return value was
missing.

How should that be fixed, via a hack in the implementation which pushes
some random value to avoid an immediate crash? And then what?

Let the user - the author of the function - explicitly provide that
value then at least that can be documented: if N isn't in 1..3, then F
returns so and so.

You know that makes perfect sense, but because you've got used to that
dangerous feature in C you think it's acceptable.
David Brown
2024-11-05 08:26:24 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Bart
But it could for n==4.
Again, you /completely/ miss the point.
If you have a function (or construct) that returns a correct value for
inputs 1, 2 and 3, and you never pass it the value 4 (or anything
else), then there is no undefined behaviour no matter what the code
looks like for values other than 1, 2 and 3.  If someone calls that
function with input 4, then /their/ code has the error - not the code
that doesn't handle an input 4.
This is the wrong kind of thinking.
If this was a library function then, sure, you can stipulate a set of
input values, but that's at a different level, where you are writing
code on top of a working, well-specified language.
You don't make use of holes in the language, one that can cause a crash.
That is, by allowing a function to run into an internal RET op with no
provision for a result. That's if there even is a RET; perhaps your
compilers are so confident that that path is not taken, or you hint it
won't be, that they won't bother!
It will start executing whatever random bytes follow the function.
As I said in my last post, a missing return value caused an internal
error in one of my C implementations because a pushed return value was
missing.
How should that be fixed, via a hack in the implementation which pushes
some random value to avoid an immediate crash? And then what?
Let the user - the author of the function - explicitly provide that
value then at least that can be documented: if N isn't in 1..3, then F
returns so and so.
You know that makes perfect sense, but because you've got used to that
dangerous feature in C you think it's acceptable.
I am a serious programmer. I write code for use by serious programmers.
I don't write code that is bigger and slower for the benefit of some
half-wit coder that won't read the relevant documentation or rub a
couple of brain cells together. I have no time for hand-holding and
spoon-feeding potential users of my functions - if someone wants to use
play-dough plastic knives, they should not have become a programmer.

My programming stems from mathematics, not from C, and from an education
in developing provably correct code. I don't try to calculate the log
of 0, and I don't expect the mathematical log function to give me some
"default" value if I try. The same applies to my code.
Bart
2024-11-06 14:40:52 UTC
Reply
Permalink
Post by David Brown
Post by Bart
Here is a summary of C vs my language.
<snip the irrelevant stuff>
Post by Bart
Post by David Brown
I am very keen on keeping the concepts distinct in cases where it
matters.
   func F:int ...              Always returns a value
   proc P  ...                 Never returns a value
Oh, you /know/ that, do you?  And how do you "know" that?  Is that
because you still think I am personally responsible for the C language,
and that I think C is the be-all and end-all of perfect languages?
I agree that it can make sense to divide different types of "function".
I disagree that whether or not a value is returned has any significant
relevance.  I see no difference, other than minor syntactic issues,
between "int foo(...)" and "void foo(int * result, ...)".
I don't use functional concepts; my functions may or may not be pure.

But the difference between value-returning and non-value returning
functions to me is significant:

Func Proc
return x; Y N
return; N Y
hit final } N Y
Pure ? Unlikely
Side-effects ? Likely
Call within expr Y N
Call standalone ? Y

Having a clear distinction helps me focus more precisely on how a
routine has to work.

In C, the syntax is dreadful: not only can you barely distinguish a
function from a procedure (even without attributes, user types and
macros add in), but you can hardly tell them apart from variable
declarations.

In fact, function declarations can even be declared in the middle of a
set of variable declarations.

You can learn a lot about the underlying structure of of a language by
implementing it. So when I generate IL from C for example, I found the
need to have separate instructions to call functions and procedures, and
separate return instructions too.
Post by David Brown
If you have a function (or construct) that returns a correct value for
inputs 1, 2 and 3, and you never pass it the value 4 (or anything else),
then there is no undefined behaviour no matter what the code looks like
for values other than 1, 2 and 3.  If someone calls that function with
input 4, then /their/ code has the error - not the code that doesn't
handle an input 4.
No. The function they are calling is badly formed. There should never be
any circumstance where a value-returning function terminates (hopefully
by running into RET) without an explicit set return value.
Post by David Brown
I agree that this a terrible idea.
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60523>
But picking one terrible idea in C does not mean /everything/ in C is a
terrible idea!  /That/ is what you got wrong, as you do so often.
What the language does is generally fine. /How/ it does is generally
terrible. (Type syntax; no 'fun' keyword; = vs ==; operator precedence;
format codes; 'break' in switch; export by default; struct T vs typedef
T; dangling 'else'; optional braces; ... there's reams of this stuff!)

So actually, I'm not wrong. There have been discussions about all of
these and a lot more.
Post by David Brown
Post by Bart
Can you tell me which other current languages, other than C++ and
assembly, allow such nonsense?
Python.
Of course, it is equally meaningless in Python as it is in C.
Python at least can trap the errors. Once you fix the unlimited
recursion, it will detect the wrong number of arguments. In C, before
C23 anyway, any number and types of arguments is legal in that example.
Post by David Brown
I defend it if that is appropriate.  Mostly, I /explain/ it to you.  It
is bizarre that people need to do that for someone who claims to have
written a C compiler, but there it is.
It is bizarre that the ins and outs of C, a supposedly simple language,
are so hard to understand. Like the rules for how many {} you can leave
out for a initialising a nested data structure. Or how many extra ones
you can have; this is OK:

int a = {0};

but not {{0}} (tcc accepts it though, so which set of rules is it using?).

Or whether it is a static followed by a non-static declaration that is
OK, or whether it's the other way around.
Post by David Brown
I'm glad you didn't - it would be a waste of effort.
I guessed that. You seemingly don't care that C is a messy language with
many quirks; you just work around it by using a subset, with some help
from your compiler in enforcing that subset.

So you're using a strict dialect. The trouble is that everyone else
using C will either be using their own dialect incompatible with yours,
or are stuck using the messy language and laid-back compilers operating
in lax mode by default.

I'm interested in fixing things at source - within a language.
Post by David Brown
You /do/ understand that I use top-quality tools with carefully chosen
warnings, set to throw fatal errors, precisely because I want a language
that has a lot more "lines" and restrictions that your little tools?
/Every/ C programmer uses a restricted subset of C - some more
restricted than others.  I choose to use a very strict subset of C for
my work, because it is the best language for the tasks I need to do.  (I
also use a very strict subset of C++ when it is a better choice.)
I'd guess only 1% of your work with C involves the actual language, and
99% using additional tooling.

With me it's mostly about the language.
David Brown
2024-11-06 15:47:50 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Bart
Here is a summary of C vs my language.
<snip the irrelevant stuff>
Post by Bart
Post by David Brown
I am very keen on keeping the concepts distinct in cases where it
matters.
   func F:int ...              Always returns a value
   proc P  ...                 Never returns a value
Oh, you /know/ that, do you?  And how do you "know" that?  Is that
because you still think I am personally responsible for the C
language, and that I think C is the be-all and end-all of perfect
languages?
I agree that it can make sense to divide different types of
"function". I disagree that whether or not a value is returned has any
significant relevance.  I see no difference, other than minor
syntactic issues, between "int foo(...)" and "void foo(int * result,
...)".
I don't use functional concepts; my functions may or may not be pure.
OK. You are not alone in that. (Standard C didn't support a difference
there until C23.)
Post by Bart
But the difference between value-returning and non-value returning
                  Func  Proc
return x;         Y     N
return;           N     Y
hit final }       N     Y
Pure              ?     Unlikely
Side-effects      ?     Likely
Call within expr  Y     N
Call standalone   ?     Y
There are irrelevant differences in syntax, which could easily disappear
entirely if a language supported a default initialisation value when a
return gives no explicit value. (i.e., "T foo() { return; }; T x =
foo();" could be treated in the same way as "T x;" in a static
initialisation context.) /Your/ language does not support that, but
other languages could.

Then you list some things that may or may not happen, which are of
course totally irrelevant. If you list the differences between bikes
and cars, you don't include "some cars are red" and "bikes are unlikely
to be blue".
Post by Bart
Having a clear distinction helps me focus more precisely on how a
routine has to work.
It's a pointless distinction. Any function or procedure can be morphed
into the other form without any difference in the semantic meaning of
the code, requiring just a bit of re-arrangement at the caller site:

int foo(int x) { int y = ...; return y; }

void foo(int * res, int x) { int y = ...; *res = y; }


void foo(int x) { ... ; return; }

int foo(int x) { ... ; return 0; }


There is no relevance in the division here, which is why most languages
don't make a distinction unless they do so simply for syntactic reasons.
Post by Bart
In C, the syntax is dreadful: not only can you barely distinguish a
function from a procedure (even without attributes, user types and
macros add in), but you can hardly tell them apart from variable
declarations.
As always, you are trying to make your limited ideas of programming
languages appear to be correct, universal, obvious or "natural" by
saying things that you think are flaws in C. That's not how a
discussion works, and it is not a way to convince anyone of anything.
The fact that C does not have a keyword used in the declaration or
definition of a function does not in any way mean that there is the
slightest point in your artificial split between "func" and "proc"
functions.


(It doesn't matter that I too prefer a clear keyword for defining
functions in a language.)
Post by Bart
In fact, function declarations can even be declared in the middle of a
set of variable declarations.
You can learn a lot about the underlying structure of of a language by
implementing it. So when I generate IL from C for example, I found the
need to have separate instructions to call functions and procedures, and
separate return instructions too.
That is solely from your choice of an IL.
Post by Bart
Post by David Brown
If you have a function (or construct) that returns a correct value for
inputs 1, 2 and 3, and you never pass it the value 4 (or anything
else), then there is no undefined behaviour no matter what the code
looks like for values other than 1, 2 and 3.  If someone calls that
function with input 4, then /their/ code has the error - not the code
that doesn't handle an input 4.
No. The function they are calling is badly formed. There should never be
any circumstance where a value-returning function terminates (hopefully
by running into RET) without an explicit set return value.
There are no circumstances where you can use the function correctly and
it does not return the correct answer. If you want to consider when
people to use a function /incorrectly/, then there are no limits to how
wrong they can be.
Post by Bart
Post by David Brown
I agree that this a terrible idea.
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60523>
But picking one terrible idea in C does not mean /everything/ in C is
a terrible idea!  /That/ is what you got wrong, as you do so often.
What the language does is generally fine. /How/ it does is generally
terrible. (Type syntax; no 'fun' keyword; = vs ==; operator precedence;
format codes; 'break' in switch; export by default; struct T vs typedef
T; dangling 'else'; optional braces; ... there's reams of this stuff!)
Making the same mistake again does not help your argument.
Post by Bart
So actually, I'm not wrong. There have been discussions about all of
these and a lot more.
Of course you are wrong!

You have failed to grasp the key concept of programming - it is based on
contracts and agreements. Tasks are broken down into subtasks, and for
each subtask there is a requirement for what gets put into the subtask
and a requirement for what comes out of it. The calling task is
responsible for fulfilling the input requirements, the callee subtask is
responsible for fulfilling the output requirements. The caller does not
need to check that the outputs are correct, and the callee does not need
to check that the input tasks are correct. That is the division of
responsibilities - and doing anything else is, at best, wasted duplicate
effort.

You are right that C has its flaws - every language does. I agree with
you in many cases where you think C has poor design choices.

But can you not understand that repeating things that you dislike about
C - things we have all heard countless times - does not excuse your
tunnel vision about programming concepts or change your misunderstandings?
Post by Bart
Post by David Brown
Post by Bart
Can you tell me which other current languages, other than C++ and
assembly, allow such nonsense?
Python.
Of course, it is equally meaningless in Python as it is in C.
Python at least can trap the errors. Once you fix the unlimited
recursion, it will detect the wrong number of arguments. In C, before
C23 anyway, any number and types of arguments is legal in that example.
It is syntactically legal, but semantically undefined behaviour (look it
up in the C standards). That means it is wrong, but the language
standards don't insist that compilers diagnose it as an error.
Post by Bart
Post by David Brown
I defend it if that is appropriate.  Mostly, I /explain/ it to you.
It is bizarre that people need to do that for someone who claims to
have written a C compiler, but there it is.
It is bizarre that the ins and outs of C, a supposedly simple language,
are so hard to understand.
Have you ever played Go ? It is a game with very simple rules, and
extraordinarily complicated gameplay.

Compared to most general purpose languages, C /is/ small and simple.
But that is a relative rating, not an absolute rating.
Post by Bart
Post by David Brown
I'm glad you didn't - it would be a waste of effort.
I guessed that. You seemingly don't care that C is a messy language with
many quirks; you just work around it by using a subset, with some help
from your compiler in enforcing that subset.
Yes.

If there was an alternative language that I thought would be better for
the tasks I have, I'd use that. (Actually, a subset of C++ is often
better, so I use that when I can.)

What do you think I should do instead? Whine in newsgroups to people
that don't write language standards (for C or anything else) and don't
make compilers? Make my own personal language that is useless to
everyone else and holds my customers to ransom by being the only person
that can work with their code? Perhaps that is fine for the type of
customers you have, but not for my customers.

I /do/ understand that C has its flaws (from /my/ viewpoint, for /my/
needs). So I work around those.
Post by Bart
So you're using a strict dialect. The trouble is that everyone else
using C will either be using their own dialect incompatible with yours,
or are stuck using the messy language and laid-back compilers operating
in lax mode by default.
I'm interested in fixing things at source - within a language.
You haven't fixed a thing.

(I'm not claiming /I/ have fixed anything either.)
Post by Bart
Post by David Brown
You /do/ understand that I use top-quality tools with carefully chosen
warnings, set to throw fatal errors, precisely because I want a
language that has a lot more "lines" and restrictions that your little
tools? /Every/ C programmer uses a restricted subset of C - some more
restricted than others.  I choose to use a very strict subset of C for
my work, because it is the best language for the tasks I need to do.
(I also use a very strict subset of C++ when it is a better choice.)
I'd guess only 1% of your work with C involves the actual language, and
99% using additional tooling.
What a weird thing to guess.
Post by Bart
With me it's mostly about the language.
An even weirder thing to say from someone who made his own tools.
Bart
2024-11-06 19:38:09 UTC
Reply
Permalink
Post by David Brown
There are irrelevant differences in syntax, which could easily disappear
entirely if a language supported a default initialisation value when a
return gives no explicit value.  (i.e., "T foo() { return; }; T x =
foo();" could be treated in the same way as "T x;" in a static
initialisation context.)
You wrote:

T foo () {return;} # definition?

T x = foo(); # call?

I'm not quite sure what you're saying here. That a missing return value
in non-void function would default to all-zeros?

Maybe. A rather pointless feature just to avoid writing '0', and which
now introduces a new opportunity for a silent error (accidentally
forgetting a return value).

It's not quite the same as a static initialisiation, which is zeroed
when a program starts.
Post by David Brown
Then you list some things that may or may not happen, which are of
course totally irrelevant.  If you list the differences between bikes
and cars, you don't include "some cars are red" and "bikes are unlikely
to be blue".
Yes; if you're using a vehicle, or planning a journey or any related
thing, it helps to remember if it's a bike or a car! At least here you
acknowledge the difference.

But I guess you find those likely/unlikely macros of gcc pointless too.
If I know something is a procedure, then I also know it is likely to
change global state, that I might need to deal with a return value, and
a bunch of other stuff.

Boldly separating the two with either FUNC or PROC denotations I find
helps tremendously. YM-obviously-V, but you can't have a go at me for my
view.

If I really found it a waste of time, the distinction would have been
dropped decades ago.
Post by David Brown
It's a pointless distinction.  Any function or procedure can be morphed
into the other form without any difference in the semantic meaning of
    int foo(int x) { int y = ...; return y; }
    void foo(int * res, int x) { int y = ...; *res = y; }
    void foo(int x) { ... ; return; }
    int foo(int x) { ... ; return 0; }
There is no relevance in the division here, which is why most languages
don't make a distinction unless they do so simply for syntactic reasons.
As I said, you like to mix things up. You disagreed. I'm not surprised.

Here you've demonstrated how a function that returns results by value
can be turned into a procedure that returns a result by reference.

So now, by-value and by-reference are the same thing?

I listed seven practical points of difference between functions and
procedures, and above is an eighth point, but you just dismissing them.
Is there any point in this?

I do like taking what some think as a single feature and having
dedicated versions, because I find it helpful.

That includes functions, loops, control flow and selections.
Post by David Brown
Post by Bart
In C, the syntax is dreadful: not only can you barely distinguish a
function from a procedure (even without attributes, user types and
macros add in), but you can hardly tell them apart from variable
declarations.
As always, you are trying to make your limited ideas of programming
languages appear to be correct, universal, obvious or "natural" by
saying things that you think are flaws in C.  That's not how a
discussion works, and it is not a way to convince anyone of anything.
The fact that C does not have a keyword used in the declaration or
definition of a function does not in any way mean that there is the
slightest point in your artificial split between "func" and "proc"
functions.
void F();
void (*G);
void *H();
void (*I)();

OK, 4 things declared here. Are they procedures, functions, variables,
or pointers to functions? (I avoided using a typedef in place of 'void'
to make things easier.)

I /think/ they are as follows: procedure, pointer variable, function
(returning void*), and pointer to a procedure. But I had to work at it,
even though the examples are very simple.

I don't know about you, but I prefer syntax like this:

proc F
ref void G
ref proc H
func I -> ref void

Now come on, scream at again for prefering a nice syntax for
programming, one which just tells me at a glance what it means without
having to work it out.
Post by David Brown
(It doesn't matter that I too prefer a clear keyword for defining
functions in a language.)
Why? Don't your smart tools tell you all that anyway?
Post by David Brown
That is solely from your choice of an IL.
The IL design also falls into place from the natural way these things
have to work.
Post by David Brown
Of course you are wrong!
You keep saying that. But then you also keep saying, from time to time,
that you agree that something in C was a bad idea. So I'm still wrong
when calling out the same thing?
Post by David Brown
If there was an alternative language that I thought would be better for
the tasks I have, I'd use that.  (Actually, a subset of C++ is often
better, so I use that when I can.)
What do you think I should do instead?  Whine in newsgroups to people
that don't write language standards (for C or anything else) and don't
make compilers?
What makes you think I'm whining? The thread opened up a discussion
about multi-way selections, and it got into how it could be done with
features from other languages.

I gave some examples from mine, as I'm very familiar with that, and it
uses simple features that are easy to grasp and appreciate. You could
have done the same from ones you know.

But you just hate the idea that I have my own language to draw on, whose
syntax is very sweet ('serious' languages hate such syntax for some
reason, and is usually relegated to scripting languages.)

I guess then you just have to belittle and insult me, my languages and
my views at every opporunity.
Post by David Brown
Make my own personal language that is useless to
everyone else and holds my customers to ransom by being the only person
that can work with their code?
Plenty of companies use DSLs. But isn't that sort of what you do? That
is, using 'C' with a particular interpretation or enforcement of the
rules, which needs to go in hand with a particular compiler, version,
sets of options and assorted makefiles.

I for one would never be able to build one of your programs. It might as
well be written in your inhouse language with proprietory tools.

Waldek Hebisch
2024-11-05 12:42:34 UTC
Reply
Permalink
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
--
Waldek Hebisch
fir
2024-11-05 13:23:04 UTC
Reply
Permalink
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
in fact when consider in mind or see on assembly level the
implementation of switch not necessary need "default"
patch (which shopuld be named "other" btw)

it has two natural ways
1) ignore them
2) signal an runtime error

(both are kinda natural)
David Brown
2024-11-05 13:29:21 UTC
Reply
Permalink
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it. I'm not sure if that is covered by "OK" or not!
Post by Waldek Hebisch
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
I think this is all very dependent on what you mean by "all input values".

Supposing I declare this function:

// Return the integer square root of numbers between 0 and 10
int small_int_sqrt(int x);


To me, the range of "all input values" is integers from 0 to 10. I
could implement it as :

int small_int_sqrt(int x) {
if (x == 0) return 0;
if (x < 4) return 1;
if (x < 9) return 2;
if (x < 16) return 3;
unreachable();
}

If the user asks for small_int_sqrt(-10) or small_int_sqrt(20), that's
/their/ fault and /their/ problem. I said nothing about what would
happen in those cases.

But some people seem to feel that "all input values" means every
possible value of the input types, and thus that a function like this
should return a value even when there is no correct value in and no
correct value out.

This is, IMHO, just nonsense and misunderstands the contract between
function writers and function users.

Further, I am confident that these people are quite happen to write code
like :

// Take a pointer to an array of two ints, add them, and return the sum
int sum_two_ints(const int * p) {
return p[0] + p[1];
}

Perhaps, in a mistaken belief that it makes the code "safe", they will add :

if (!p) return 0;

at the start of the function. But they will not check that "p" actually
points to an array of two ints (how could they?), nor will they check
for integer overflow (and what would they do if it happened?).



A function should accept all input values - once you have made clear
what the acceptable input values can be. A "default" case is just a
short-cut for conveniently handling a wide range of valid input values -
it is never a tool for handling /invalid/ input values.
Waldek Hebisch
2024-11-05 19:39:21 UTC
Reply
Permalink
Post by David Brown
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it. I'm not sure if that is covered by "OK" or not!
You may prefer your own definition, but Bart's is resonable one.
Post by David Brown
Post by Waldek Hebisch
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
I think this is all very dependent on what you mean by "all input values".
// Return the integer square root of numbers between 0 and 10
int small_int_sqrt(int x);
To me, the range of "all input values" is integers from 0 to 10. I
int small_int_sqrt(int x) {
if (x == 0) return 0;
if (x < 4) return 1;
if (x < 9) return 2;
if (x < 16) return 3;
unreachable();
}
If the user asks for small_int_sqrt(-10) or small_int_sqrt(20), that's
/their/ fault and /their/ problem. I said nothing about what would
happen in those cases.
But some people seem to feel that "all input values" means every
possible value of the input types, and thus that a function like this
should return a value even when there is no correct value in and no
correct value out.
Well, some languages treat types more seriously than C. In Pascal
type of your input would be 0..10 and all input values would be
handled. Sure, when domain is too complicated to express in type
than it could be documented restriction. Still, it makes sense to
signal error if value goes outside handled rage, so in a sense all
values of input type are handled: either you get valid answer or
clear error.
Post by David Brown
This is, IMHO, just nonsense and misunderstands the contract between
function writers and function users.
Further, I am confident that these people are quite happen to write code
// Take a pointer to an array of two ints, add them, and return the sum
int sum_two_ints(const int * p) {
return p[0] + p[1];
}
I do not think that people wanting strong type checking are happy
with C. Simply, either they use different language or use C
without bitching, but aware of its limitations. I certainly would
be quite unhappy with code above. It is possible that I would still
use it as a compromise (say if it was desirable to have single
prototype but handle points in spaces of various dimensions),
but my first attempt would be something like:

typedef struct {int p[2];} two_int;
....
Post by David Brown
if (!p) return 0;
at the start of the function. But they will not check that "p" actually
points to an array of two ints (how could they?), nor will they check
for integer overflow (and what would they do if it happened?).
I am certainly unhappy with overflow handling in current hardware
an by extention with overflow handling in C.
Post by David Brown
A function should accept all input values - once you have made clear
what the acceptable input values can be. A "default" case is just a
short-cut for conveniently handling a wide range of valid input values -
it is never a tool for handling /invalid/ input values.
Well, default can signal error which frequently is right handling
of invalid input values.
--
Waldek Hebisch
David Brown
2024-11-05 20:33:55 UTC
Reply
Permalink
Post by Waldek Hebisch
Post by David Brown
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it. I'm not sure if that is covered by "OK" or not!
You may prefer your own definition, but Bart's is resonable one.
The only argument I can make here is that I have not seen "multi-way
select" as a defined phrase with a particular established meaning. So
it simply means what the constituent words mean - selecting something
from multiple choices. There are no words in that phrase that talk
about "branching", or imply a specific order to events. It is a very
general and vague phrase, and I cannot see a reason to assume it has
such a specific meaning as Bart wants to assign to it. And as I have
pointed out in other posts, there are constructs in many languages
(including C) that fit the idea of a selection from one of many things,
but which do not fit Bart's specific interpretation of the phrase.

Bart's interpretation is "reasonable" in the sense of being definable
and consistent, or at least close enough to that to be useable in a
discussion. But neither he, I, or anyone else gets to simply pick a
meaning for such a phrase and claim it is /the/ definition. Write a
popular and influential book with this as a key phrase, and /then/ you
can start calling your personal definition "the correct" definition.
Post by Waldek Hebisch
Post by David Brown
Post by Waldek Hebisch
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
I think this is all very dependent on what you mean by "all input values".
// Return the integer square root of numbers between 0 and 10
int small_int_sqrt(int x);
To me, the range of "all input values" is integers from 0 to 10. I
int small_int_sqrt(int x) {
if (x == 0) return 0;
if (x < 4) return 1;
if (x < 9) return 2;
if (x < 16) return 3;
unreachable();
}
If the user asks for small_int_sqrt(-10) or small_int_sqrt(20), that's
/their/ fault and /their/ problem. I said nothing about what would
happen in those cases.
But some people seem to feel that "all input values" means every
possible value of the input types, and thus that a function like this
should return a value even when there is no correct value in and no
correct value out.
Well, some languages treat types more seriously than C. In Pascal
type of your input would be 0..10 and all input values would be
handled. Sure, when domain is too complicated to express in type
than it could be documented restriction. Still, it makes sense to
signal error if value goes outside handled rage, so in a sense all
values of input type are handled: either you get valid answer or
clear error.
No, it does not make sense to do that. Just because the C language does
not currently (maybe once C++ gets contracts, C will copy them) have a
way to specify input sets other than by types, does not mean that
functions in C always have a domain matching all possible combinations
of bits in the underlying representation of the parameter's types.

It might be a useful fault-finding aid temporarily to add error messages
for inputs that are invalid but can physically be squeezed into the
parameters. That won't stop people making incorrect declarations of the
function and passing completely different parameter types to it, or
finding other ways to break the requirements of the function.

And in general there is no way to check the validity of the inputs - you
usually have no choice but to trust the caller. It's only in simple
cases, like the example above, that it would be feasible at all.


There are, of course, situations where the person calling the function
is likely to be incompetent, malicious, or both, and where there can be
serious consequences for what you might prefer to consider as invalid
input values. You have that for things like OS system calls - it's no
different than dealing with user inputs or data from external sources.
But you handle that by extending the function - increase the range of
valid inputs and appropriate outputs. You no longer have a function
that takes a number between 0 and 10 and returns the integer square root
- you now have a function that takes a number between -(2 ^ 31 + 1) and
(2 ^ 31) and returns the integer square root if the input is in the
range 0 to 10 or halts the program with an error message for other
inputs in the wider range. It's a different function, with a wider set
of inputs - and again, it is specified to give particular results for
particular inputs.
Post by Waldek Hebisch
Post by David Brown
This is, IMHO, just nonsense and misunderstands the contract between
function writers and function users.
Further, I am confident that these people are quite happen to write code
// Take a pointer to an array of two ints, add them, and return the sum
int sum_two_ints(const int * p) {
return p[0] + p[1];
}
I do not think that people wanting strong type checking are happy
with C. Simply, either they use different language or use C
without bitching, but aware of its limitations.
Sure. C doesn't give as much help to writing correct programs as some
other languages. That doesn't mean the programmer can't do the right thing.
Post by Waldek Hebisch
I certainly would
be quite unhappy with code above. It is possible that I would still
use it as a compromise (say if it was desirable to have single
prototype but handle points in spaces of various dimensions),
typedef struct {int p[2];} two_int;
....
I think you'd quickly find that limiting and awkward in C (but it might
be appropriate in other languages). But don't misunderstand me - I am
all in favour of finding ways in code that make input requirements
clearer or enforceable within the language - never put anything in
comments if you can do it in code. You could reasonably do this in C
for the first example :


// Do not use this directly
extern int small_int_sqrt_implementation(int x);


// Return the integer square root of numbers between 0 and 10
static inline int small_int_sqrt(int x) {
assert(x >= 0 && x <= 10);
return small_int_sqrt_implementation(x);
}


There is no way to check the validity of pointers in C, but you might at
least be able to use implementation-specific extensions to declare the
function with the requirement that the pointer not be null.
Post by Waldek Hebisch
Post by David Brown
if (!p) return 0;
at the start of the function. But they will not check that "p" actually
points to an array of two ints (how could they?), nor will they check
for integer overflow (and what would they do if it happened?).
I am certainly unhappy with overflow handling in current hardware
an by extention with overflow handling in C.
Post by David Brown
A function should accept all input values - once you have made clear
what the acceptable input values can be. A "default" case is just a
short-cut for conveniently handling a wide range of valid input values -
it is never a tool for handling /invalid/ input values.
Well, default can signal error which frequently is right handling
of invalid input values.
Will that somehow fix the bug in the code that calls the function?

It can be a useful debugging and testing aid, certainly, but it does not
make the code "correct" or "safe" in any sense.
Bart
2024-11-05 23:01:44 UTC
Reply
Permalink
Post by David Brown
Post by Waldek Hebisch
Post by David Brown
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it.  I'm not sure if that is covered by "OK" or not!
You may prefer your own definition, but Bart's is resonable one.
The only argument I can make here is that I have not seen "multi-way
select" as a defined phrase with a particular established meaning.
Well, it started off as 2-way select, meaning constructs like this:

x = c ? a : b;
x := (c | a | b)

Where one of two branches is evaluated. I extended the latter to N-way
select:

x := (n | a, b, c, ... | z)

Where again one of these elements is evaluated, selected by n (here
having the values of 1, 2, 3, ... compared with true, false above, but
there need to be at least 2 elements inside |...| to distinguish them).

I applied it also to other statements that can be provide values, such
as if-elsif chains and switch, but there the selection might be
different (eg. a series of tests are done sequentially until a true one).

I don't know how it got turned into 'multi-way'.

Notice that each starts with an assignment (or the value is used in
other ways like passing to a function), so provision has to be made for
some value always to be returned.

Such N-way selections can be emulated, for example:

if (c)
x = a;
else
x = b;

But because the assignment has been brought inside (a dedicated one for
each branch), the issue of a default path doesn't arise. You can leave
out the 'else' for example; x is just left unchanged.

This doesn't work however when the result is passed to a function:

f(if (c) a);

what is passed when c is false?
Kaz Kylheku
2024-11-06 07:26:25 UTC
Reply
Permalink
Post by Bart
Post by David Brown
Post by Waldek Hebisch
Post by David Brown
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it.  I'm not sure if that is covered by "OK" or not!
You may prefer your own definition, but Bart's is resonable one.
The only argument I can make here is that I have not seen "multi-way
select" as a defined phrase with a particular established meaning.
x = c ? a : b;
x := (c | a | b)
Where one of two branches is evaluated. I extended the latter to N-way
x := (n | a, b, c, ... | z)
This looks quite error-prone. You have to count carefully that
the cases match the intended values. If an entry is
inserted, all the remaining ones shift to a higher value.

You've basically taken a case construct and auto-generated
the labels starting from 1.

If that was someone's Lisp macro, I would prefer they confine
it to their own program. :)

1> (defmacro nsel (expr . clauses)
^(caseql ,expr ,*[mapcar list 1 clauses]))
nsel
2> (nsel 1 (prinl "one") (prinl "two") (prinl "three"))
"one"
"one"
3> (nsel (+ 1 1) (prinl "one") (prinl "two") (prinl "three"))
"two"
"two"
4> (nsel (+ 1 3) (prinl "one") (prinl "two") (prinl "three"))
nil
5> (nsel (+ 1 2) (prinl "one") (prinl "two") (prinl "three"))
"three"
"three"
nil
6> (macroexpand-1 '(nsel x a b c d))
(caseql x (1 a)
(2 b) (3 c)
(4 d))

Yawn ...
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
Bart
2024-11-06 10:01:16 UTC
Reply
Permalink
Post by Kaz Kylheku
Post by Bart
x = c ? a : b;
x := (c | a | b)
Where one of two branches is evaluated. I extended the latter to N-way
x := (n | a, b, c, ... | z)
This looks quite error-prone. You have to count carefully that
the cases match the intended values. If an entry is
inserted, all the remaining ones shift to a higher value.
You've basically taken a case construct and auto-generated
the labels starting from 1.
It's a version of Algol68's case construct:

x := CASE n IN a, b, c OUT z ESAC

which also has the same compact form I use. I only use the compact
version because n is usually small, and it is intended to be used within
an expression: print (n | "One", "Two", "Three" | "Other").

This an actual example (from my first scripting language; not written by
me):

Crd[i].z := (BendAssen |P.x, P.y, P.z)

An out-of-bounds index yields 'void' (via a '| void' part inserted by
the compiler). This is one of my examples from that era:

xt := (messa | 1,1,1, 2,2,2, 3,3,3)
yt := (messa | 3,2,1, 3,2,1, 3,2,1)

Algol68 didn't have 'switch', but I do, as well as a separate
case...esac statement that is more general. Those are better for
multi-line constructs.

As for being error prone because values can get out of step, so is a
function call like this:

f(a, b, c, d, e)

But I also have keyword arguments.
David Brown
2024-11-06 07:38:47 UTC
Reply
Permalink
Post by David Brown
Post by Waldek Hebisch
Post by David Brown
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
I appreciate this is what Bart means by that phrase, but I don't agree
with it.  I'm not sure if that is covered by "OK" or not!
You may prefer your own definition, but Bart's is resonable one.
The only argument I can make here is that I have not seen "multi-way
select" as a defined phrase with a particular established meaning.
   x = c ? a : b;
   x := (c | a | b)
Where one of two branches is evaluated. I extended the latter to N-way
   x := (n | a, b, c, ... | z)
I appreciate that this is what you have in your language as a "multi-way
select". I can see it being a potentially useful construct (though
personally I don't like the syntax at all).

The only thing I have disagreed with is your assertions that what you
have there is somehow the only "true" or "correct" concept of a
"multi-way selection".
Bart
2024-11-05 22:48:28 UTC
Reply
Permalink
Post by David Brown
// Return the integer square root of numbers between 0 and 10
int small_int_sqrt(int x);
To me, the range of "all input values" is integers from 0 to 10.  I
int small_int_sqrt(int x) {
    if (x == 0) return 0;
    if (x < 4) return 1;
    if (x < 9) return 2;
    if (x < 16) return 3;
    unreachable();
}
If the user asks for small_int_sqrt(-10) or small_int_sqrt(20), that's
/their/ fault and /their/ problem.  I said nothing about what would
happen in those cases.
But some people seem to feel that "all input values" means every
possible value of the input types, and thus that a function like this
should return a value even when there is no correct value in and no
correct value out.
Your example is an improvement on your previous ones. At least it
attempts to deal with out-of-range conditions!

However there is still the question of providing that return type. If
'unreachable' is not a special language feature, then this can fail
either if the language requires the 'return' keyword, or 'unreachable'
doesn't yield a compatible type (even if it never returns because it's
an error handler).

Getting that right will satisfy both the language (if it cared more
about such matters than C apparently does), and the casual reader
curious about how the function contract is met (that is, supplying that
promised return int type if or when it returns).
Post by David Brown
// Take a pointer to an array of two ints, add them, and return the sum
int sum_two_ints(const int * p) {
    return p[0] + p[1];
}
    if (!p) return 0;
at the start of the function.  But they will not check that "p" actually
points to an array of two ints (how could they?), nor will they check
for integer overflow (and what would they do if it happened?).
This is a different category of error.

Here's a related example of what I'd class as a language error:

int a;
a = (exit(0), &a);

A type mismatch error is usually reported. However, the assignment is
never done because it never returns from that exit() call.

I expect you wouldn't think much of a compiler that didn't report such
an error because that code is never executed.

But to me that is little different from running into the end of function
without the proper provisions for a valid return value.
David Brown
2024-11-06 14:50:21 UTC
Reply
Permalink
Post by Bart
Post by David Brown
// Return the integer square root of numbers between 0 and 10
int small_int_sqrt(int x);
To me, the range of "all input values" is integers from 0 to 10.  I
int small_int_sqrt(int x) {
     if (x == 0) return 0;
     if (x < 4) return 1;
     if (x < 9) return 2;
     if (x < 16) return 3;
     unreachable();
}
If the user asks for small_int_sqrt(-10) or small_int_sqrt(20), that's
/their/ fault and /their/ problem.  I said nothing about what would
happen in those cases.
But some people seem to feel that "all input values" means every
possible value of the input types, and thus that a function like this
should return a value even when there is no correct value in and no
correct value out.
Your example is an improvement on your previous ones. At least it
attempts to deal with out-of-range conditions!
No, it does not. The fact that some invalid inputs also give
deterministic results is a coincidence of the implementation, not an
indication that the function is specified for those additional inputs or
that it does any checking. I intentionally structured the example this
way to show this - sometimes undefined behaviour gives you results you
might like, but it is still undefined behaviour. This function has no
defined behaviour for inputs outside the range 0 to 10, because I gave
no definition of its behaviour - the effect of particular
implementations of the function is irrelevant to that.

As I suspected it might, this apparently confused you.
Post by Bart
However there is still the question of providing that return type. If
'unreachable' is not a special language feature, then this can fail
either if the language requires the 'return' keyword, or 'unreachable'
doesn't yield a compatible type (even if it never returns because it's
an error handler).
"unreachable()" is a C23 standardisation of a feature found in most
high-end compilers. For gcc and clang, there is
__builtin_unreachable(), and MSVC has its version. The functions are
handled by the compilers as "undefined behaviour". (I mean that quite
literally - gcc and clang turn it into an "UB" instruction in their
internal representations.)

Clearly, "unreachable()" has no return type - it does not in any sense
"return". And since the compiler knows it will never be "executed", it
knows control will never fall off the end of that function. You don't
need a type for something that can never happen (it's like if I say
"this is a length of 0" and you ask "was that 0 metres, or 0 inches?" -
the question is meaningless).
Post by Bart
Getting that right will satisfy both the language (if it cared more
about such matters than C apparently does), and the casual reader
curious about how the function contract is met (that is, supplying that
promised return int type if or when it returns).
C gets it right here. There is no need for a return type when there is
no return - indeed, trying to force some sort of type or "default" value
would be counterproductive. It would be confusing to the reader, add
untestable and unexecutable source code, make code flow more
complicated, break invariants, cripple correctness analysis of the rest
of the code, and make the generated object code inefficient.

Remember how the function is specified. All you have to do is use it
correctly - go outside the specifications, and I make no promises or
guarantees about what will happen. If you step outside /your/ side of
the bargain by giving it an input outside 0 to 10, then I give you no
more guarantees that it will return an int of any sort than I give you a
guarantee that it would be a great sales gimmick if printed on a t-shirt.

But what I /can/ give you is something that can be very useful in being
sure the rest of your code is correct, and which is impossible for a
function with "default" values or other such irrelevant parts. I can
guarantee you that:

int y = small_int_sqrt(x);

assert(y * y <= x);
assert ((y + 1) * (y + 1) > x);


That is to say - I can guarantee that the function works and gives you
the correct results.

But supposing I had replaced the "unreachable();" with a return of a
default value - let's say 42, since that's the right answer even if you
don't know the question. What does the user of small_int_sqrt() know now?

Now you know that "y" is an int. You have no idea if it is a correct or
useful result, unless you have first checked that x is in the specified
range of 0 to 10.

If you /have/ checked (in some way) that x is valid, then why would you
bother calling the function when x is invalid? And thus why would you
care what the function does or does not do when x is invalid?

And if you haven't checked that x is valid, why would you bother calling
the function if you have no idea whether or not it results in something
useful and correct?


So we have now established that returning a default int value is worse
than useless - there are no circumstances in which it can be helpful,
and it ruins the guarantees you want in order to be sure that the
calling code is correct.


Let's now look at another alternative - have the function check for
validity, and return some kind of error signal if the input is invalid.
There are two ways to do this - we can have a value of the main return
type acting as an error signal, or we can have an additional return value.

If we pick the first one - say, return -1 on error - then we have a
compact solution that is easy to check for the calling function. But
now we have a check for validity of the input whether we need it or not
(since the callee function does the checking, even if the caller
function knows the values are valid), and the caller function has to add
a check a check for error return values. The return may still be an
"int", but it is no longer representative of an integer value - it
multiplexes two different concepts. We have lost the critical
correctness equations that we previously had. And it won't work at all
if there is no choice of an error indicator.

If we pick the second one, we need to return two values. The checking
is all the same, but at least the concepts of validity and value are
separated. Now we have either a struct return with its added efficiency
costs, or a monstrosity from the dark ages where the function must take
a pointer parameter for where to store the results. (And how is the
function going to check the validity of that pointer? Or is it somehow
okay to skip that check while insisting that a check of the other input
is vital?) This has most of the disadvantages of the first choice, plus
extra efficiency costs.


All in all, we have a significant costs in various aspects, with no real
benefit, all in the name of a mistaken belief that we are avoiding
undefined behaviour.
Post by Bart
Post by David Brown
// Take a pointer to an array of two ints, add them, and return the sum
int sum_two_ints(const int * p) {
     return p[0] + p[1];
}
     if (!p) return 0;
at the start of the function.  But they will not check that "p"
actually points to an array of two ints (how could they?), nor will
they check for integer overflow (and what would they do if it happened?).
This is a different category of error.
No, it is not. It is just another case of a function having
preconditions on the input, and whether or not the called function
should check those preconditions. You can say you think it is vital for
functions to do these checks itself, or you can accept that it is the
responsibility of the calling code to provide valid inputs. But you
don't get to say it is vital to check /some/ types of inputs, but other
types are fine to take on trust.
Post by Bart
   int a;
   a = (exit(0), &a);
A type mismatch error is usually reported. However, the assignment is
never done because it never returns from that exit() call.
I expect you wouldn't think much of a compiler that didn't report such
an error because that code is never executed.
I would expect the compiler to know that "exit()" can't return, so the
value of "a" is never used and it can be optimised away. But I do also
expect that the compiler will enforce the rules of the language - syntax
and grammar rules, along with constraints and anything else it is able
to check. And even if I said it was reasonable for a language to say
this "assignment" is not an error since it can't be executed, I think
trying to put that level of detail into a language definition (and
corresponding compilers) would quickly be a major complexity for no
real-world gain.
Post by Bart
But to me that is little different from running into the end of function
without the proper provisions for a valid return value.
Yes, I think so too.
Bart
2024-11-05 15:03:54 UTC
Reply
Permalink
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
What's easier to implement in a language: to have a conditional need for
an 'else' branch, which is dependent on the compiler performing some
arbitrarily complex levels of analysis on some arbitrarily complex set
of expressions...

...or to just always require 'else', with a dummy value if necessary?

Even if you went with the first, what happens if the compiler can't
guarantee that all values of a selector are covered; should it report
that, or say nothing?

What happens if you do need 'else', but later change things so all bases
are covered; will the compiler report it as being unnecesary, so that
you remove it?


Now, C doesn't have such a feature to test out (ie. that is a construct
with an optional 'else' branch, the whole of which returns a value). The
nearest is function return values:

int F(int n) {
if (n==1) return 10;
if (n==2) return 20;
}

Here, neither tcc not gcc report that you might run into the end of the
function. It will return garbage if called with anything other than 1 or 2.

gcc will say something with enough warning levels (reaches end of
non-void function). But it will say the same here:

int F(unsigned char c) {
if (c<128) return 10;
if (c>=128) return 20;
}
David Brown
2024-11-05 16:02:04 UTC
Reply
Permalink
Post by Bart
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values.  This is possible when there
is reasonably small number of possibilities.  For example, switch on
char variable which covers all possible values does not need default
path.  Default is needed only when number of possibilities is too
large to explicitely give all of them.  And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
What's easier to implement in a language: to have a conditional need for
an 'else' branch, which is dependent on the compiler performing some
arbitrarily complex levels of analysis on some arbitrarily complex set
of expressions...
...or to just always require 'else', with a dummy value if necessary?
If this was a discussion on learning about compiler design for newbies,
that might be a relevant point. Otherwise, what is easier to implement
in a language tool is completely irrelevant to what is good in a language.

A language should try to support things that are good for the
/programmer/, not the compiler. But it does have to limited by what is
practically possible for a compiler. A fair bit of the weaknesses of C
as a language can be attributed to the limitations of compilers from its
early days, and thereafter existing practice was hard to change.
Waldek Hebisch
2024-11-05 19:53:12 UTC
Reply
Permalink
Post by Bart
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
What's easier to implement in a language: to have a conditional need for
an 'else' branch, which is dependent on the compiler performing some
arbitrarily complex levels of analysis on some arbitrarily complex set
of expressions...
...or to just always require 'else', with a dummy value if necessary?
Well, frequently it is easier to do bad job, than a good one. However,
normally you do not need very complex analysis: if simple analysis
is not enough, then first thing to do is to simpliy the program.
And in cases where problem to solve is really hard and program can
not be simplified ("irreducible complexity"), then it is time for
cludges for example in form of default case. But it should not
be the norm.
Post by Bart
Even if you went with the first, what happens if the compiler can't
guarantee that all values of a selector are covered; should it report
that, or say nothing?
Compile time error.
Post by Bart
What happens if you do need 'else', but later change things so all bases
are covered; will the compiler report it as being unnecesary, so that
you remove it?
When practical, yes.
Post by Bart
Now, C doesn't have such a feature to test out (ie. that is a construct
with an optional 'else' branch, the whole of which returns a value). The
int F(int n) {
if (n==1) return 10;
if (n==2) return 20;
}
Here, neither tcc not gcc report that you might run into the end of the
function. It will return garbage if called with anything other than 1 or 2.
Hmm, using gcc-12 with your code in "foo.c":

gcc -Wall -O3 -c foo.c
foo.c: In function ‘F’:
foo.c:4:1: warning: control reaches end of non-void function [-Wreturn-type]
4 | }
| ^
Post by Bart
gcc will say something with enough warning levels (reaches end of
int F(unsigned char c) {
if (c<128) return 10;
if (c>=128) return 20;
}
Indeed, it says the same. Somebody should report this as a bug.
IIUC gcc has all machinery needed to detect that all cases are
covered.
--
Waldek Hebisch
Bart
2024-11-05 23:15:35 UTC
Reply
Permalink
Post by Waldek Hebisch
Post by Bart
Post by Waldek Hebisch
Post by Bart
Then we disagree on what 'multi-way' select might mean. I think it means
branching, even if notionally, on one-of-N possible code paths.
OK.
Post by Bart
The whole construct may or may not return a value. If it does, then one
of the N paths must be a default path.
You need to cover all input values. This is possible when there
is reasonably small number of possibilities. For example, switch on
char variable which covers all possible values does not need default
path. Default is needed only when number of possibilities is too
large to explicitely give all of them. And some languages allow
ranges, so that you may be able to cover all values with small
number of ranges.
What's easier to implement in a language: to have a conditional need for
an 'else' branch, which is dependent on the compiler performing some
arbitrarily complex levels of analysis on some arbitrarily complex set
of expressions...
...or to just always require 'else', with a dummy value if necessary?
Well, frequently it is easier to do bad job, than a good one.
I assume that you consider the simple solution the 'bad' one?

I'd would consider a much elaborate one putting the onus on external
tools, and still having an unpredictable result to be the poor of the two.

You want to create a language that is easily compilable, no matter how
complex the input.

With the simple solution, the worst that can happen is that you have to
write a dummy 'else' branch, perhaps with a dummy zero value.

If control never reaches that point, it will never be executed (at
worse, it may need to skip an instruction).

But if the compiler is clever enough (optionally clever, it is not a
requirement!), then it could eliminate that code.

A bonus is that when debugging, you can comment out all or part of the
previous lines, but the 'else' now catches those untested cases.
I don't want to do any analysis at all! I just want a mechanical
translation as effortlessly as possible.

I don't like unbalanced code within a function because it's wrong and
can cause problems.
fir
2024-11-01 13:05:01 UTC
Reply
Permalink
Post by Bart
#define or else if
if (x == a) {}
or (x == b) {}
or (x == c) {}
thsi coud have some sense if this or would be buildin keyword
(but not saying "enough" sense to do that, as writing this
logical "buildings" in code overally is not nice

overally this ilustrates the interesting difference its like
difference in

if a|b|c|d {}

and

if a {} | if b {} | if c {} | if d {}


where | means or

this means that both such ors are needed imo (but id doesnt necessary
mean that building that logical constructions is good imo) (i mean there
are complex way of writing code and simpel pialn ones and plain ones may
be better, if there is some plain one here)
Tim Rentsch
2024-11-02 18:09:42 UTC
Reply
Permalink
Post by fir
ral clear patterns here: you're testing the same variable 'n'
against several mutually exclusive alternatives, which also happen
to be consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers
or even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it
optically, swich case i also dont like (use as far i i remember
never in my code, for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
I think you should have confidence in your own opinion. All
you're getting from other people is their opinion about what is
easier to understand, or "clear", or "readable", etc. As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.

There is a case where using 'else' is necessary, when there is a
catchall action for circumstances matching "none of the above".
Alternatively a 'break' or 'continue' or 'goto' or 'return' may
be used to bypass subsequent cases, but you get the idea.

With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
Tim Rentsch
2024-11-02 21:25:53 UTC
Reply
Permalink
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
that should be

With the understanding that I am offering nothing more than my own opinion,
fir
2024-11-03 00:53:16 UTC
Reply
Permalink
Post by Tim Rentsch
Post by fir
ral clear patterns here: you're testing the same variable 'n'
against several mutually exclusive alternatives, which also happen
to be consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers
or even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it
optically, swich case i also dont like (use as far i i remember
never in my code, for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
I think you should have confidence in your own opinion. All
you're getting from other people is their opinion about what is
easier to understand, or "clear", or "readable", etc. As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.
There is a case where using 'else' is necessary, when there is a
catchall action for circumstances matching "none of the above".
Alternatively a 'break' or 'continue' or 'goto' or 'return' may
be used to bypass subsequent cases, but you get the idea.
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
Tim Rentsch
2024-11-04 04:00:55 UTC
Reply
Permalink
Post by fir
Post by Tim Rentsch
Post by fir
ral clear patterns here: you're testing the same variable 'n'
against several mutually exclusive alternatives, which also happen
to be consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers
or even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it
optically, swich case i also dont like (use as far i i remember
never in my code, for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
I think you should have confidence in your own opinion. All
you're getting from other people is their opinion about what is
easier to understand, or "clear", or "readable", etc. As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.
There is a case where using 'else' is necessary, when there is a
catchall action for circumstances matching "none of the above".
Alternatively a 'break' or 'continue' or 'goto' or 'return' may
be used to bypass subsequent cases, but you get the idea.
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
David Brown
2024-11-04 07:18:34 UTC
Reply
Permalink
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
Post by fir
ral clear patterns here: you're testing the same variable 'n'
against several mutually exclusive alternatives, which also happen
to be consecutive values.
C is short of ways to express this, if you want to keep those
'somethings' as inline code (otherwise arrays of function pointers
or even label pointers could be use
so in short this groupo seem to have no conclusion but is tolerant
foir various approaches as it seems
imo the else latder is like most proper but i dont lkie it
optically, swich case i also dont like (use as far i i remember
never in my code, for years dont use even one)
so i persnally would use bare ifs and maybe elses ocasionally
(and switch should be mended but its fully not clear how,
I think you should have confidence in your own opinion. All
you're getting from other people is their opinion about what is
easier to understand, or "clear", or "readable", etc. As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.
There is a case where using 'else' is necessary, when there is a
catchall action for circumstances matching "none of the above".
Alternatively a 'break' or 'continue' or 'goto' or 'return' may
be used to bypass subsequent cases, but you get the idea.
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
There have been /some/ justifications for some of the opinions - but
much of it has been merely opinions. And other people's opinions and
thoughts can be inspirational in forming your own opinions.

Once the OP (or anyone else) has looked at these, and garnered the ideas
floated around, he might then modify his own opinions and preferences as
a result. In the end, however, you are right that it is the OP's own
opinions and preferences that should guide the style of the code - only
he knows what the real code is, and what might suit best for the task in
hand.
Bart
2024-11-04 11:56:03 UTC
Reply
Permalink
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.

Somebody may try to argue about a particular approach or feature being
more readable, easier to understand, to implement, more ergonomic, more
intuitive, more efficient, more maintainable etc, but they are never
going to convince anyone who has a different view or who is too used to
another approach.

In this case, it was about how to express a coding pattern in a
particular language, as apparently the OP didn't like writing the 'else'
in 'else if', and they didn't like using 'switch'.

You are trying to argue against somebody's personal preference; that's
never going to go well. Even when you use actual facts, such as having
the wrong behaviour when those 'somethings' do certain things.

Here, the question was, can:

if (c1) s1;
else if (c2) s2;

always be rewritten as:

if (c1) s1;
if (c2) s2;

In general, the answer has to be No. But when the OP doens't like that
answer, what can you do?

Even when the behaviour is the same for a particular set of c1/c2/s1/s2,
the question then was: is it always going to be as efficient (since c2
may be sometimes be evaluated unnessarily). Then it depends on quality
of implementation, another ill-defined factor.
Janis Papanagnou
2024-11-04 12:29:09 UTC
Reply
Permalink
Post by Bart
[...]
if (c1) s1;
else if (c2) s2;
if (c1) s1;
if (c2) s2;
Erm, no. The question was even more specific. It had (per example)
not only all ci disjunct but also defined as a linear sequence of
natural numbers! - In other languages [than "C"] this may be more
important since [historically] there were specific constructs for
that case; see e.g. 'switch' definitions in Simula, or the 'case'
statement of Algol 68, both mapping elements onto an array[1..N];
labels in the first case, and expressions in the latter case. So
in "C" we could at least consider using something similar, like,
say, arrays of function pointers indexed by those 'n'. (Not that
I'd suggest that by just pointing it out.)

I'm a bit astonished, BTW, about this huge emphasis on the topic
"opinions" in later posts of this thread. The OP asked (even in
the subject) about "practice" which actually invites if not asks
for providing opinions (besides practical experiences).

(He also asked about two specific aspects; performance and terse
code. Answers to that can already be derived from various posts'
answers.)

Janis
Post by Bart
[...]
Bart
2024-11-04 12:38:06 UTC
Reply
Permalink
Post by Janis Papanagnou
Post by Bart
[...]
if (c1) s1;
else if (c2) s2;
if (c1) s1;
if (c2) s2;
Erm, no. The question was even more specific.
I mean that the question came down to this. After all he had already
decided on that second form rather than the first, and had acknowledged
that the 'else's were missing.

That the OP's example contained some clear patterns has already been
covered (I did so anyway).
Post by Janis Papanagnou
It had (per example)
not only all ci disjunct but also defined as a linear sequence of
natural numbers! - In other languages [than "C"] this may be more
important since [historically] there were specific constructs for
that case; see e.g. 'switch' definitions in Simula, or the 'case'
statement of Algol 68, both mapping elements onto an array[1..N];
labels in the first case, and expressions in the latter case. So
in "C" we could at least consider using something similar, like,
say, arrays of function pointers indexed by those 'n'.
That too!

! (Not that
Post by Janis Papanagnou
I'd suggest that by just pointing it out.)
I'm a bit astonished, BTW, about this huge emphasis on the topic
"opinions" in later posts of this thread. The OP asked (even in
the subject) about "practice" which actually invites if not asks
for providing opinions (besides practical experiences).
Janis Papanagnou
2024-11-04 12:46:34 UTC
Reply
Permalink
Post by Bart
That the OP's example contained some clear patterns has already been
covered (I did so anyway).
I haven't read every post, even if I occasionally take some time
to catch up.[*]

Janis

[*] Threads in this group, even for trivial things, tend to get
band-worms and individual posts often very longish.
fir
2024-11-04 15:02:16 UTC
Reply
Permalink
Post by Bart
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
overally when you think and discuss such thing some conclusions may do
appear - and often soem do for me, though they are not always very clear
or 'hard'

overally from this thread i noted that switch (which i already dont
liked) is bad.. note those two elements of switch it is "switch"
and "Case" are in weird not obvious relation in c (and what will it
work when you mix it etc)

what i concluded was than if you do thing such way


a { } //this is analogon to case - named block
b { } //this is analogon to case - named block
n() // here by "()" i noted call of some wariable that mey yeild
'call' to a ,b, c, d, e, f //(in that case na would be soem enum or
pointer)
c( ) //this is analogon to case - named block
d( ) //this is analogon to case - named block


then everything is clear - this call just selects and calls block , and
block itself are just definitions and are skipped in execution until
"called"


this is example of some conclusion for me from thsi thread - and i think
such codes as this my own initial example should be probably done such
way (though it is not c, i know
fir
2024-11-04 15:06:56 UTC
Reply
Permalink
Post by fir
Post by Bart
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
overally when you think and discuss such thing some conclusions may do
appear - and often soem do for me, though they are not always very clear
or 'hard'
overally from this thread i noted that switch (which i already dont
liked) is bad.. note those two elements of switch it is "switch"
and "Case" are in weird not obvious relation in c (and what will it
work when you mix it etc)
what i concluded was than if you do thing such way
a { } //this is analogon to case - named block
b { } //this is analogon to case - named block
n() // here by "()" i noted call of some wariable that mey yeild
'call' to a ,b, c, d, e, f //(in that case na would be soem enum or
pointer)
c( ) //this is analogon to case - named block
d( ) //this is analogon to case - named block
then everything is clear - this call just selects and calls block , and
block itself are just definitions and are skipped in execution until
"called"
this is example of some conclusion for me from thsi thread - and i think
such codes as this my own initial example should be probably done such
way (though it is not c, i know
note in fact both array usage like tab[5] and fuunction call like foo()
are analogues to swich case - as when you call fuctions the call is like
switch and function definition sets are 'cases'
Bart
2024-11-04 15:21:37 UTC
Reply
Permalink
Post by fir
Post by fir
Post by Bart
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances.  I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
overally when you think and discuss such thing some conclusions may do
appear - and often soem do for me, though they are not always very clear
or 'hard'
overally from this thread i noted that switch (which i already dont
liked) is bad.. note those two elements of switch it is "switch"
and "Case" are in weird not obvious relation in c (and what will it
work when you mix it etc)
what i concluded was than if you do thing such way
a {  }  //this is analogon to case - named block
b {  }  //this is analogon to case - named block
n()   // here by "()" i noted call of some wariable that mey yeild
'call' to a ,b, c, d, e, f  //(in that case na would be soem enum or
pointer)
c(  ) //this is analogon to case - named block
d(  ) //this is analogon to case - named block
then everything is clear - this call just selects and calls block , and
block itself are just definitions and are skipped in execution until
"called"
this is example of some conclusion for me from thsi thread - and i think
such codes as this my own initial example should be probably done such
way (though it is not c, i know
note in fact both array usage like tab[5] and fuunction call like foo()
are analogues to swich case - as when you call fuctions the call is like
switch and function definition sets are 'cases'
Yes, switch could be implemented via a table of label pointers, but it
needs a GNU extension.

For example this switch:

#include <stdio.h>

int main(void) {
for (int i=0; i<10; ++i) {
switch(i) {
case 7: case 2: puts("two or seven"); break;
case 5: puts("five"); break;
default: puts("other");
}
}
}


Could also be written like this:

#include <stdio.h>

int main(void) {
void* table[] = {
&&Lother, &&Lother, &&L27, &&Lother, &&Lother, &&L5,
&&Lother, &&L27, &&Lother, &&Lother};

for (int i=0; i<10; ++i) {
goto *table[i];

L27: puts("two or seven"); goto Lend;
L5: puts("five"); goto Lend;
Lother: puts("other");
Lend:;
}
}

(A compiler may generate something like this, although it will be
range-checked if need. In practice, small numbers of cases, or where the
case values are too spread out, might be implemented as if-else chains.)
fir
2024-11-04 15:52:17 UTC
Reply
Permalink
Post by Bart
Post by fir
Post by fir
Post by Bart
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
overally when you think and discuss such thing some conclusions may do
appear - and often soem do for me, though they are not always very clear
or 'hard'
overally from this thread i noted that switch (which i already dont
liked) is bad.. note those two elements of switch it is "switch"
and "Case" are in weird not obvious relation in c (and what will it
work when you mix it etc)
what i concluded was than if you do thing such way
a { } //this is analogon to case - named block
b { } //this is analogon to case - named block
n() // here by "()" i noted call of some wariable that mey yeild
'call' to a ,b, c, d, e, f //(in that case na would be soem enum or
pointer)
c( ) //this is analogon to case - named block
d( ) //this is analogon to case - named block
then everything is clear - this call just selects and calls block , and
block itself are just definitions and are skipped in execution until
"called"
this is example of some conclusion for me from thsi thread - and i think
such codes as this my own initial example should be probably done such
way (though it is not c, i know
note in fact both array usage like tab[5] and fuunction call like foo()
are analogues to swich case - as when you call fuctions the call is
like switch and function definition sets are 'cases'
Yes, switch could be implemented via a table of label pointers, but it
needs a GNU extension.
#include <stdio.h>
int main(void) {
for (int i=0; i<10; ++i) {
switch(i) {
case 7: case 2: puts("two or seven"); break;
case 5: puts("five"); break;
default: puts("other");
}
}
}
#include <stdio.h>
int main(void) {
void* table[] = {
&&Lother, &&Lother, &&L27, &&Lother, &&Lother, &&L5,
&&Lother, &&L27, &&Lother, &&Lother};
for (int i=0; i<10; ++i) {
goto *table[i];
L27: puts("two or seven"); goto Lend;
L5: puts("five"); goto Lend;
Lother: puts("other");
Lend:;
}
}
(A compiler may generate something like this, although it will be
range-checked if need. In practice, small numbers of cases, or where the
case values are too spread out, might be implemented as if-else chains.)
probably swich is implemented like

push __out__ //to simulate return under out_ adress
cmp eax, "A"
je __A__
cmp eax, "B"
je __B__
cmp eax, "C"
je __C__
__out___:
...
...
...

if elkse ladder would do the same i guess
and sequence f ifs would not push __out__ if
not detected it can ans those cases for sure may not appear

its waste to check a long sequance of compares it someones unlucky
though if the argument of switch is like 8bit wide it is probably no
problem to put the labels in the teble and callvia the table
fir
2024-11-04 15:34:46 UTC
Reply
Permalink
Post by fir
Post by Bart
Post by Tim Rentsch
Post by fir
Post by Tim Rentsch
With the understanding that I am offering more than my own opinion,
I can say that I might use any of the patterns mentioned, depending
on circumstances. I don't think any one approach is either always
right or always wrong.
maybe, but some may heve some strong arguments (for use this and not
that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
overally when you think and discuss such thing some conclusions may do
appear - and often soem do for me, though they are not always very clear
or 'hard'
overally from this thread i noted that switch (which i already dont
liked) is bad.. note those two elements of switch it is "switch"
and "Case" are in weird not obvious relation in c (and what will it
work when you mix it etc)
what i concluded was than if you do thing such way
a { } //this is analogon to case - named block
b { } //this is analogon to case - named block
n() // here by "()" i noted call of some wariable that mey yeild
'call' to a ,b, c, d, e, f //(in that case na would be soem enum or
pointer)
c( ) //this is analogon to case - named block
d( ) //this is analogon to case - named block
second wersion would be the one based on labels and goto

a:
b:
n!
c:
d:

gere n! wuld symbolize goto n and the different operator means dfference
among "call" ang "jmp" on assembly level and lack of block
would denote lack on ret on assembly level


im not sure byut maybe that those two versions span all needed
(not sure as to this, but as said one sxpresses jumps and one calls
on assembly level
Post by fir
then everything is clear - this call just selects and calls block , and
block itself are just definitions and are skipped in execution until
"called"
this is example of some conclusion for me from thsi thread - and i think
such codes as this my own initial example should be probably done such
way (though it is not c, i know
Tim Rentsch
2024-11-05 14:11:18 UTC
Reply
Permalink
Post by Bart
Post by Tim Rentsch
Post by fir
With the understanding that I am offering [nothing] more than my
own opinion, I can say that I might use any of the patterns
mentioned, depending on circumstances. I don't think any one
approach is either always right or always wrong.
maybe, but some may heve some strong arguments (for use this and
not that) i may overlook
I acknowledge the point, but you haven't gotten any arguments,
only opinions.
Pretty much everything about PL design is somebody's opinion.
First, the discussion is not about language design but language
usage.

Second, the idea that "pretty much everything" about language usage
is just opinion is simply wrong (that holds for language design
also). Most of what is offered in newsgroups is just opinion, but
there are plenty of objective statements that could be made also.
Posters in the newsgroup here rarely make such statements, mostly I
think because they don't want to be bothered to make the effort to
research the issues. But that doesn't mean there isn't much to say
about such things; there is plenty to say, but for some strange
reason the people posting in comp.lang.c think their opinions offer
more value than statements of objective fact.
Janis Papanagnou
2024-11-04 12:40:48 UTC
Reply
Permalink
[...] As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.
This is certainly true for one-man-shows. Hardly suited for most
professional contexts I worked in. (Just my experience, of course.
And people are free to learn things the Hard Way, if they prefer.)

Janis
Tim Rentsch
2024-11-05 13:50:34 UTC
Reply
Permalink
Post by Janis Papanagnou
[...] As long as
the code is logically correct you are free to choose either
style, and it's perfectly okay to choose the one that you find
more appealing.
This is certainly true for one-man-shows.
The question asked concerned code in an individual programming
effort. I was addressing the question that was asked.
Post by Janis Papanagnou
Hardly suited for most professional contexts I worked in.
Note that the pronoun "you" is plural as well as singular. The
conclusion applies to groups just as it does to individuals.
Loading...