Post by BartPost by David BrownPost by BartPost by David BrownPost by BartPost by David BrownWhat 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 BartA 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 BartThe 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 BartPost by David BrownI 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 BartOne 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 BartSo, 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 BartPost by David BrownPost by BartIn 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 BartPost 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 BartSo 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 BartC '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 BartCase 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 BartThe 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 BartYou 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 BartPost by David BrownYou 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 BrownThe 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 BartThe 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 BartYOU 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 BartPost by David BrownSo 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 BrownYou 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 BartYou 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 BrownPost 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 BartPost by David BrownPost by BartBut 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 BartPost 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 BartIf 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 BartThis 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 BartPost by David BrownPost by BartSOMETHING 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 BartYou 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 BartThe 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 BrownPost by BartIn 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 BartThis 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 BartPost by David BrownI 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 BartPost by David BrownPost 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 BartIt'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 BartThere 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 BartPost by David BrownI 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 BartMisleading 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 BartAnd 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.