Any reason to not keep the good old for loop format?

In my case I wasn’t suggesting that foreach is what you should use, but instead just contrasting internal vs external iteration.

As far as why you should not just switch to foreach, it has to do with the functional programming principles and particularly trying to eliminate side effects. For a foreach to actually do anything usefull the only way it can do that is by causing side effects.

Similarly I suppose that is another knock against the for loop that it is by definition against the princible because it too cannot do anything without the use of side effects,

I’m not going to go into the why behind the principle. For that consult references on functional programming.

I’ve read this discussion with some interest. There appears to be an assumption that C-style for loops is the most flexible and comprehensive way to implement loops, and all other variants are workarounds to cover carious use-cases of the traditional style.

The reason C’s for is the way it is is because of hardware limitations back in 1972 when it was implemented on a PDP-7. This is no reason to hail it as being the optimal form of looping.

A lot of good arguments in favour of more extensive syntax in Kotlin, most of which I agree with. For most cases, there are already better ways to loop in Kotlin that are much more clear.

For the rest of this message, I’d like to address the argument that the C version is somehow flexible enough to do everything. This is simply not true, and if one looks further than just Java, there are much more interesting languages to take ideas from.

As someone who has been using Common Lisp for quite some time, the idea that the C-style loop is somehow comprehensive is a bit ridiculous. CL has arguably the most powerful looping construct of all languages, and if the goal is to be comprehensive, then CL is the direction that one should look, not C.

I’ve provide an example just to be clear. The CL loop construct uses keywords to control behaviour, which is somewhat controversial even in CL circles because it makes it very verbose, but let’s focus on the functionality and not the actual syntax used to provide it.

Note that the newlines in the syntax are there for readability, you can write it all on one line if you want to.

First off, the traditional for(int i = 0 ; i < 5 ; i++) { print(i); }:

(loop
  for i from 0 below 5
  do (print i))

Now, let’s loop i from 0 to a, and j from 0 to b, ending whenever either of them reach their bound. So basically for(int i = 0, j = 0 ; i < a && j < b ; i++, j++) { foo(i,j); }:

(loop
  for i from 0 to a
  for j from 0 to b
  do (foo i j))

How about something like the following in Java?

var list = new ArrayList<Integer>();
for(int i = 0 ; i < 5 ; i++) {
  list.add(i);
}

The CL version:

(loop
  for i from 0 below 5
  collect i)

Here’s an example of a counter as well as an iteration over a list:

(loop
  for i from 0 below a  ; Loops from 0 until a
  for v in list         ; Set v to each element in list
  until (equal v nil)   ; If v is nil, then stop looping
  collect (list i v))   ; Add a list containing the values i and v to the result

You can also combine exit checks before, after and in the middle of the loop. To do this in C you’d have to use break:

(loop
  for v in list
  while (some-test)
  collect v
  until (some-other-test)
  do (print v))

This is just a small sample of the things you can do with CL loop. Here’s the full documentation if anyone is interested: CLHS: Section 6.1

3 Likes

Ohh, interesting stuff about this CL loop. Kotlin (any many other languages) went into another direction: declarative collection/stream transformations. I think most/all above examples could be rewritten to a very similar code using such transformations and at least to me they look more clean. In many cases, they seem more flexible as well.

Many of your examples iterate two counters at the same time. I’m surprised they decided to use above syntax for this case, which I believe isn’t that common. Instead, they could use it for nested loops, similarly to Python:

In [1]: [(x, y) for x in range(2) for y in range(3)]
Out[1]: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
1 Like

The reason for the syntax is because the loop form always represents a single loop. Nested loops have to use explicitly nested loop invocations. When used, a loop invocation consists of a set of statements, where each statement introduces some new rules. These rules includes things like iteration (from, to/unbounded, step size, etc). exit criteria (while/until), return value (return), conditionals (if/when/unless), variable updates (for/with), accumulation values (collect, sum, max, min).

Generally (with some exceptions), these statements are independent so it wouldn’t really make sense to have multiple iteratior construct result in nested loops. If it did, something like this (which is very common) wouldn’t work:

(loop
  for v in list                       ; Loop over all elements in list
  for i from 0                        ; Keep a counter from 0 (this is an unbounded index)
  do (format t "Index ~d: ~s~%" i v)) ; Print the index and the value

The link I sent is to the specification, and may be quite difficult to read for someone who is not familiar with CL. The following link is better for someone who is just learning the language: The Common Lisp Cookbook - The Powerful 'loop' Macro

Of course, CL also provides functional iteration forms such mapping and reduction, but those are outside the scope of loop.

Interestingly enough, CL also provides a separate looping construct which is much older than loop called do. In terms of functionality it’s very close to the C for loop. However, thanks to the existence of loop, no one really uses do. Here’s the documentation for it. I find it incredibly cumbersome to use, and I rarely see it in actual code: CLHS: Macro DO, DO*

The creation of an iterator is actually not just a penalty, a loop with an iterator is safer, because it will protect list modification during iteration using ConcurrentModificationException
It’s one of the reasons why for each loop in java uses an iterator too

So you can iterate using indexes (like proposed forEach extension), if you know well how your code works and by whom it is used and you doing some hard optimizations, but it doesn’t make it suitable for general language constract

Also about " that we are not supposed to just replace fors with forEach", It would be interesting to see a context of this statement, because I agree with it if we talk in absolutely abstract way, yes, for sure you shouldn’t just replace it with forEach, because you may replace it with many other more specialized extensions, but if we talk in cotext of for(int i = 1; i < array.size; i++) then I really don’t see any issue with forEach

I only compared for (item in list) and list.forEach {}. But let’s just ignore what I said. I have no idea where did I read this, who said this, what was the context and if I didn’t at all imagine this.

for (item in list) also creates iterator, the only way to avoid it is to use manual index-based iteration

Of course it does use iterators. As a matter of fact, forEach() is implemented as for (item in list), so it is effectively the same.

My loop in c#

 for (int cnt = 0; cnt <16; cnt ++, newMask >>= 1, oldMask >>=1)
  {
      if ((newMask & 1) == (oldMask & 1))
      {
          continue;
      }
      int status = (newMask & 1);
      int bitMask = 1 << cnt;
      // more code...
  }

Same loop, translated in kotlin:

                var cnt = 0
                while (cnt < 16) {
                    if (newMask and 1 === oldMask and 1) {
                        cnt++
                        newMask = newMask shr 1
                        oldMask = oldMask shr 1
                        continue
                    }
                    val status = newMask and 1
                    val bitMask = 1 shl cnt
                    // more code...
                    cnt++
                    newMask = newMask shr 1
                    oldMask = oldMask shr 1
                }

The Kotlin version seems less readable and more prone to errors.

In my opinion, you shouldn’t be using Kotlin to write that kind of code. It’s highly mutable and using weird bit masking stuff, which I don’t really think Kotlin is designed to handle.

The problem is: your loop in c# is entirely unreadable, at least to me. It took me quite a bit of time to understand it. It is easy to see what it does do if you know what it does upfront, but after 6 months we need to reverse-engineer our own code.

This is not entirely your fault. Such complicated looping logic where we walk over multiple variables together is hard to grasp by humans. In Kotlin or functional languages for clarity we prefer to explicitly separate the way how the state mutates between iterations, end conditions, conditions for each item, and the loop body which is often additionally split into multiple steps. But it is still not that easy to implement the above and make the code clean. We could use a mix of generateSequence, zip, runningFold, etc., but it would be a mess anyway.

I would probably create a separate class to keep the current state and then the solution looks much cleaner for me than the original loop, but technically it requires more code:

// or whatever name makes the most sense
data class Iteration(val cnt: Int, val newMask: Int, val oldMask: Int)
fun Iteration.next() = Iteration(cnt + 1, newMask shr 1, oldMask shr 1)

generateSequence(Iteration(0, 255, 157), Iteration::next)
    .take(16)
    .filter { it.newMask and 1 != it.oldMask and 1 }
    .forEach {
        ...
    }

We can do it without additional class, by using Triple and providing the mutation logic directly in generateSequence, but for me it is much less readable.

Also, if you still prefer imperative programming, mutable variables, etc and your main concern is that you can’t easily modify variables at the end of each iteration, then you can try putting cnt++ etc in finally. I don’t know if this is a common pattern, sounds definitely strange :slight_smile:

1 Like

The big avantage of the C-style for loop is that it has the third block, that captures the natural notion of “prepare the next iteration”, in a very general way, in my opinion. The different syntaxes of the loops in Kotlin however deal with a lot of specialized cases; a step back, I think.

It is definitely a matter of taste. For me more specialized means cleaner, better. And there are many other examples:

  • filter() - specialized if + continue.
  • map() - specialized: new collection, transforming and copying.
  • for - specialized while + initialization + state mutation.
  • while - specialized jumps and conditional branching.
  • if - specialized jumps and conditional branching

We identified recurring usage patterns of jumps, so we invented if/while, so we don’t have to deal with gotos directly. This is the same here: we identified recurring patterns of loops, so we created more specialized tools. We don’t have to interpret gotos to understand all they do is to run in a loop. Similarly, we don’t have to interpret ifs and continues to understand all they do is simple filtering - we have filter() for that purpose.

However, I agree this improvement due to specialization doesn’t fit your initial example nicely. It is hard to write this code cleanly, no matter which technique we choose.

1 Like

Many of the times I start writing a for (), I find the code can be expressed more clearly (and usually more concisely) with a map or similar functional call instead.

For us programmers who first learned imperative languages (such as traditional Java, C, or even BASIC), it’s natural to reach for for () (or while ()) any time we want to loop through a structure or range or anything else repetitive. But we have better tools available now, and so it pays to relearn and review old habits.

Of course, there are still situations that don’t match one of those more specialised, more readable functions, and so we still need general-purpose loops — but in my experience, they’re not too common.

Kotlin still has the while () loop, which can do everything an old-style for () loop can, as the example above demonstrates — although there, even though the while version is longer, it’s arguably clearer, as it doesn’t bundle three unrelated variables into the same control statement!

1 Like