8
$\begingroup$

While the C-style for loop syntax is badly designed from a don't repeat yourself perspective, it does offer an advantage over many other languages' loop designs: it makes it easy to specify that a loop should exit when the index reaches a certain value, as opposed to indicating that it should execute once more after that happens. Code written in other languages is often rife with constructs like:

For I:=0 to Limit-1 Do

which ends up being more work for the programmer and compiler alike than would a loop which syntactically indicated that the loop should exit as soon as I reaches Limit.

Do any languages other than C provide nice means of indicating whether or not the "index equals boundary" case should mark the end of a loop? What about indicating that the ending value should be included but the start value shouldn't (common in "count down to zero" loops)?

To clarify the question, I'm interested in languages whose iteration control statements would allow specification of the loop start value, increment amount, direction, the stop value, and whether or not the iteration reachining the stop value should be executed.

$\endgroup$

3 Answers 3

4
$\begingroup$

C's for loop has three parts: an initialiser for an iteration variable, a condition to check if there are any more values to iterate over, and a "step" which advances the iteration variable to its next value.

More modern languages tend to encapsulate those three things into a single "iterator" object, so that loops can be written more simply by just providing an iterator (or iterable) object for the variable to take its values from. As well as simplifying the syntax of loops, this can also mean that iterator objects can be used in other ways, e.g. as arguments to higher-order functions like map and filter ─ and iterable range objects have other uses like membership tests.

So the approach of languages like this is to provide iterable objects which represent ranges of numbers. The most common types of range are

  • Half-open, including a but not b, e.g. range(a, b) in Python, a..b in Rust.
  • Closed, including both a and b, e.g. range(a, b+1) in Python, a..=b in Rust.
  • Open, including neither a nor b, e.g. range(a+1, b) in Python, (a+1)..b in Rust.
  • The other kind of half-open, including b but not a, e.g. range(a+1, b+1) in Python, (a+1)..=b in Rust.

Notably, the syntax in both cases is not so neat for the less common types of range, but this is probably more to do with their relative rarity rather than the difficulty of providing syntax:

  • Python could just as well have separate constructors for range, closed_range, open_range and so on, or they could be static methods like range.closed, or the kind of range could be controlled by optional keyword arguments like range(a, b, closed_start=False, closed_end=True).
  • Rust could support a<..b and a<..=b as syntaxes for other types of range. This would be implemented by just having <.. and <..= as individual tokens to represent range constructor operations, just as .. and ..= already are.

This also gets more complicated when you want ranges with different step sizes, including reversed ranges. Python's syntax is better for this than a Rust-style syntax would be: the range constructor takes an optional third argument like range(a, b, step). Another benefit of having range constructors be named functions is that they are easier to search for, whereas punctuation like <..= can't be put into most search engines when seen by a user unfamiliar with them.

$\endgroup$
15
  • $\begingroup$ All of those feel needlessly clunky. It would seem cleaner to extending C-like syntax to say that if the the first term of a for loop declares/defines exactly one object, the second term may start with a comparison operator and the third term may start with an indrement, decrement, or compound assignment operator; in such cases, the second or third term would behave as though it started with the item that was declared. An alternative would be to say that a for loop may include a single term if it's only used with compound statements... $\endgroup$
    – supercat
    Commented Jul 2, 2023 at 17:58
  • $\begingroup$ ...and such statements may be separated by predicates containing expressions. Requiring compound statements would allow predictates to be marked with non-reserved words, since no other identifiers could appear in such contexts. $\endgroup$
    – supercat
    Commented Jul 2, 2023 at 17:59
  • $\begingroup$ @supercat If you have an idea for an alternative which extends the C-like syntax, please write it in an answer. I do not follow what you mean about predicates and compound statements, but you would be able to explain that in an answer if you wrote one. $\endgroup$
    – kaya3
    Commented Jul 2, 2023 at 18:01
  • $\begingroup$ My question was whether any nice syntactic forms exist. I would think that in the last 50 years, someone smarter than me should have come up with something nicer than anything I could come up with, even if it never managed to gain much traction (perhaps it was a great feature in a language that wasn't well suited for popular tasks). $\endgroup$
    – supercat
    Commented Jul 2, 2023 at 18:13
  • $\begingroup$ Your question asks "Do any languages other than C provide nice means of indicating whether or not the "index equals boundary" case should mark the end of a loop?", and that's the distinction between a..b and a..=b ranges in Rust. Whether or not it's nice is a matter of opinion, but the Rust designers evidently thought it was nice enough to include in the language. In any case, the content of your comments looks like a proposal for a different syntax which belongs in an answer, not a request for clarification or suggesting an improvement for this answer. $\endgroup$
    – kaya3
    Commented Jul 2, 2023 at 18:35
2
$\begingroup$

Included for completeness you could use the mathematical notation of different bracket types for open and closed ranges. E.g.

[a,b]
[a,b)
(a,b)
(a,b]

See https://en.wikipedia.org/wiki/Interval_(mathematics)

This is anathema to most programmers as we like our brackets to balance.

$\endgroup$
1
$\begingroup$

I had thought about it, and my idea was

for (lhs relop1 variable relop2 rhs) {
}

relop1 and relop2 are either both <= or <, or both >= or >. lhs and rhs are evaluate once. variable iterates over all integer values of the right type where both relops are true, in ascending order for < / <= and descending order for > / >=.

For example

char* s; size_t i;
for (strlen (s) > i >= 0) {
}

would cleanly operate on all string elements in reverse order, stopping properly at i = 0.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .