Liste des Groupes | Revenir à cl c |
On 02/11/2024 11:41, David Brown wrote: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.On 01/11/2024 20:47, Bart wrote: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.On 01/11/2024 18:47, David Brown wrote:>On 01/11/2024 19:05, Bart wrote:>On 01/11/2024 17:35, David Brown wrote:>>>>
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.
I was also trying to show that all elements are evaluated, so each has to have some side-effect to illustrate that.
Fair enough.
>>>
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.
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.
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.)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.Those logical operators that may or may not short-circuit.
>
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.
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.
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.
That seems an arbitrary choice - the controlling expressions for the conditionals also need to be evaluated.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.>>
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 described it in a single sentence. Though I skipped some details, it covered enough for almost all real-world uses of "switch".(Of course generated code can evaluate all sorts of things in different orders, as long as observable behaviour - side-effects - are correct.)It's pretty much the complete opposite of straightforward, as you go on to demonstrate.
>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.
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.
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.
The only place they can't go is within a further nested switch, which has its own set of case-labels.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.
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.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.)
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.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.
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).It wasn't.
>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.
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.
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.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). "
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!So does that mean you are happy that C has language features for short-circuit evaluation in particular cases?
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.>>
(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.
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.These things tend to come about because that is the natural order that comes through. It's something I observed rather than decided.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.
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.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.They have to be compatible in some way, according to the language rules.
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) :
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?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!";
}
}>I write code like this:
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.
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".)
The rule instead is simple: where a multi-path construct yields a value, then it needs the default branch, always.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.)
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.)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.)
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.
It seems you are just arguing in the defence of C rather than objectively, and being contradictory in the process.>>
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;
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.
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!Then RTFM to confirm what I write about C.
Now *I* am wary of trusting your judgement.
And I've seen a three-legged dog. But generally, I'd say that dogs have four legs.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 :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.
>
if (!p) puts("error");
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.This is new to me. So this is another possibility for the OP?Meanwhile I allow this (if I was keen on a compact form):>
>
(p = nil | print "error")
>
No else is needed.
>
In C you could write :
>
p == NULL || puts("error");
>
which is exactly the same structure.
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.)
It is also error prone as it is untuitive: you probably meant one of: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.
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.Sure. In C, that's fine for most commonly used types. (Again, I don't recommend the syntax.)
(Note that the '|' is my example is not 'or'; it means 'then':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".
( 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.)
if (a) {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.This is not rocket science. I've known ten year olds that complain less about how difficult it is to learn programming.
And the whole thing cannot return a value; a separate ?: feature (whose branches must be expressions) is needed.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.
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.
Les messages affichés proviennent d'usenet.