C/C++/Java all have constructs for a loop that always happens once with a test at the bottom for whether it recurs, but it’s expressed as do … while. So a do-until loop would just be:
do {
/* loop body */
} while (!condition);
So maybe you’ve used them but (understandably) just never thought of them as a distinct concept from do…while.
(For Python, the most obvious choice would be the equivalent of the I6 generated by my I7 do begin; [...] until <condition>; end do; above… make an infinite loop and test for whether to break at the bottom.)
It’s not weird – many languages don’t have an until loop. (C++, Java, and Python are all among those that don’t. I don’t know Visual Basic but I’d be surprised if it had one.) The only one I know of off the top of my head that does have until is Perl. But most languages have an unary boolean not operator, which means until(whatever) is trivially realized as while(not whatever). In contrast, Inform 7 does not have an unary boolean not operator[1], so in my opinion that makes it much more important for it to have until. With conditional statements, we have unless, so there’s no issue there. But with loops, we lack until, so there’s no easy way to make a loop that runs until a condition is false.
Huh? No, checking the condition at the end is not a “feature” of an until loop. It’s literally just the negation of the condition. Checking the condition at the end is a separate thing, so you could say there’s 4 types – while, do ... while, until, and do ... until. I think it is pretty uncommon for a language to support all 4 though, and Inform 6 is far from the only one that decided to negate their do ... statement, ie to have do ... until but not do ... while (Lua is another case).
You can negate a relation test in the normal natural language way, eg “the sun is bright” becomes “the sun is not bright”. But you cannot negate a phrase. For example, in darkness is a phrase, and there is no not in darkness, so you can’t directly test the negation of this phrase. This particular example would never be used in a loop though, so unless fills the job here. ↩︎
True, that’s a possibility (and should have only one ~ I’m pretty sure?), but I think I prefer an until loop and maybe a negative variant of whether or not. As a random example, I think this:
until T matches the regular expression "[a-z]+":
flows better than this:
while no T matches the regular expression "[a-z]"+:
or this:
while not T matches the regular expression "[a-z]+":
Yes, of course until is much nicer than while no. But every so often when making a compound conditional an actual logical not is useful. Unfortunately, there’s no good English equivalent to “not” and while the compiler will accept To decide if not (c - condition): [...], that seems like asking, nay, begging for trouble. So I borrowed “no” from Spanish; in English it’s somewhat awkward, but does convey the correct meaning. (Though if one wanted to burn it all to the ground, one could use To decide if ! (c - condition): [...].)
Many things about I6 syntax are like that, unfortunately. Inform started as an assembler with some special syntax for things that were too annoying to write by hand, and grew organically from there, without any real design or planning in advance. So a lot of its syntax is shaped by “it would be logical for it to work this way, but that conflicts with something else, so it has to work this way instead”.
Doubling the operator for logical operations is familiar across programming languages: & is bitwise AND, && is logical AND, etc. That’s true in C and C++ as well as I6.
Where I6 got pushed off the rails was not using ! for logical NOT, because that was already in use as the comment character. Using ~~ is following a pattern that C/C++ don’t stick to, really.
While that is indeed familiar, I suspect most people don’t really think of it as “doubling the operator for logical operations”. And even then, there’s a world of difference between doubling a binary operator and doubling an unary operator. You can’t really conceive of “A && B” as “executing & twice”, but “executing ~ twice” is a natural interpretation for “~~A”.
Double bitwise complement is also not a useful operation, unlike double logical negation (which effectively casts anything to a boolean; this is used in many places in the I7-to-I6 translation step).
I suppose you have a point there, actually. That said, who would want to negate a number twice in a row? Similar to what @Draconis just said about bitwise complement.
Technically there is a use case for that—that’s why C and C++ have the unary + operator. It’s just such an obscure use case that nobody ever really uses it.
Sure it is: just like how logical not only operates on booleans, so double logical not forces a cast to boolean, arithmetic negation only operates on numerics, so double arithmetic negation forces a cast to numeric.
In practice, though, the actual need for this is vanishingly rare. So even though C provides an operator for it (unary +), most programmers will go their whole careers without ever needing to use it. There are very few reasons to use it instead of a simple (int).
(The same would apply to bitwise complement, if that only operated on specific types, but generally bitwise operations will operate on the raw bits of whatever you give them, regardless of its semantics.)
I definitely don’t think of the double ampersand for logical and and double pipe for logical or when writing compound conditionals in C++ as doubling the bitwise operators… in fact, I’m pretty sure this is the first I’m hearing of the bitwise operators actually having symbols in the syntax and while I’m pretty sure doing bitwise operations on binary numbers by hand came up somewhere in my education, I don’t think I’ve actually used them in a program I’ve written, or if I have, it was some small programming exercise and thinking at that low a level just doesn’t come up that often when coding for modern hardware
The use of double tilde for logical not would definitely throw me for a loop(pun unintended) with how used I am to not being bang.
Yeah, in the earliest versions of C, there were no logical boolean operators, only bitwise ones: & for bitwise and, | for bitwise or, ^ for bitwise xor. This is why these operators have such low precedence: when & is the only way to do a logical and, x & y == 1 needed to parse as x & (y == 1). So that’s how it still parses today.
But repurposing the bitwise operators as logical ones caused all sorts of problems. For example, x & (y == 1) would fail if y was 1 and x was 2! And it also prevented any sort of short-circuiting, which was very important for optimization. So they added doubled versions of the & and | operators (but not ^), and now everyone just has to live with the single & and | operators having an absurdly low precedence.