Kotlin is even more confusing than js :)


#1

Consider this classic js gotcha (js captures scope chain elements not variables in closures)

var result = [];
var i = 0;
for (j = 0; j <= 5; j++) {
    i++;
    result.push(function () { console.log(i + " " + j) });
}

for (var el of result) {
    el();
}

Output is as “expected”

6 6
6 6
6 6
6 6
6 6
6 6

Now lets run equivalent kotlin code

fun main(args: Array<String>) {
    val result = mutableListOf<() -> Unit>()
    var i = 0
    for (j in 0..5) {
        i++
        result += { println("$i $j") }
    }
    
    result.forEach { it() }
}

6 0
6 1
6 2
6 3
6 4
6 5

huh? :crazy_face:
why_not_both.jpg :slight_smile:


#2

I think the reason for Kotlin’s ‘unexpected’ behavior is that i is a ‘var’ whereas j is (in effect) a ‘val’ which gets assigned a new value each time around the loop.

So only the current value of ‘j’ is captured by the lambda.


#3

I guess, it is just another reason to finally kill for loop. The j variable does not really exist. The value with this name is created for each iteration. This is the reason for the confusion.


#4

So what do you suggest to use instead of a for loop. (0..5).forEach {...} produces the same output.


#5

#6

It does, but from it you expect this result.


#7

Did you mean to write the following?

        val result = mutableListOf<() -> Unit>()
        var i = 0
        for (j in 0..5) {
            i++
            result += "$i $j".let { { println(it)}  }
        }

        result.forEach { it() }

Result is

1 0
2 1
3 2
4 3
5 4
6 5

#8

I tend to use the ‘while’ statement a lot more when programming in Kotlin than I would when programming in languages which have a traditional C-style ‘for’ statement, particularly when translating code from the latter languages.

This is because there are things which Kotlin’s sanitized ‘for’ statement can’t do compared to the traditional one, including mutating the control variable. I don’t consider this to be a shortcoming in Kotlin because I think it leads to clearer code and less silly mistakes.

So I think this example is just another case where you should use ‘while’ to reproduce the sort of behavior you’d get with a traditional ‘for’. If you remember that the control variable in a Kotlin ‘for’ statement is read-only, it shouldn’t be a problem.

Incidentally, I certainly wouldn’t support getting rid of Kotlin’s ‘for’ statement which is harmless enough and can do things which can’t be done (or can’t be done as simply) by ‘forEach’.


#9

I also would not like to remove the Kotlin for loop and force the use of forEach, but what exactly can a for loop which can not be done using forEach. In my experience they are 100% interchangeable and just depend on the style of the program.


#10

break and continue.


#11

Forgot about break. You can simulate continue using return@forEach if I am not mistaken.


#12

Sure I can simulate break and continue by returning Boolean from lambda.
But it is just looks ugly…

From my real project:

    private inline fun ByteArray.nextODRAWTag(pos: Int) = pos + 8 + dword(pos + 4)

    private inline fun ByteArray.eachODRAWTag(start: Int, action: ByteArray.(Int) -> Boolean) {
        val limit = start + 8 + dword(start + 4)
        var pos = start + 8
        while (pos < limit && action(pos) == false)
            pos = nextODRAWTag(pos)
    }

...
                        eachODRAWTag(blips) bs@{ bs ->
                            if (uword(bs + 2) != ID_OfficeArtBStoreContainer)
                                return@bs false
                            eachODRAWTag(bs) fbse@{ fbse ->
                                if (uword(fbse + 2) != ID_OfficeArtFBSE)
                                    return@fbse false
                                val spid = spidIndex[++blip] ?: return@fbse false
                                val cpos = cposIndex[spid] ?: return@fbse false
                                val size = dword(fbse + 28);
                                val fpos = dword(fbse + 36);
                                images[cpos] = Image(fpos, size)
                                return@fbse false
                            }
                            return@bs true
                        }
                    }

#13

In my opinion, break and continue are only should be used for very tight optimization in native projects, even there, I am not sure that manual optimization is better then kotlin internal optimization.


#14

Personally, I miss the complex Java for loop. Taking a cue from “Effective Java” on for loops doing a better job than while loops in limiting variable scope, I had some pretty complex for loops. But, yes, they were kind of ugly. Kotlin more than makes up for that “loss,” of course.


#15

Yes, it’s true that you can’t limit the scope of the ‘control variable’ in a while statement like you can in a for statement which is a bit of a down-side.

On the other hand, if you do want to properly capture the control variable in a lambda for some reason, then while's your man.:slightly_smiling_face:


#16

Yes, just try to write this and it won’t compile. You will get an error saying “j” is a val.

for (j in 0..5) {
    j = 123
}

#17

Why would j change? An iterated value is, by design, immutable. For several dozen good reasons which I could go into (such as the fact that iterators are abstract things and can be implemented in any number of ways and imposing mutability on them would be horrible), but mostly because it makes sense. Why would you have roundabout access to some internal counting mechanism that has nothing to do with the concept of looping over a range?


#18

Whilst I agree that it was the correct decision for the Kotlin designers to make the iteration variable read-only, it may still come as a surprise to those with experience in other C-family languages (C/C++/Java/C#/JavaScript etc) where it’s mutable.


#19

I think var(in js) similar as var(in kotlin) and let(in js) similar as val(in kotlin), so if you change the js example to use let, it has the same result:
yarco_%E2%80%94_-bash_%E2%80%94_156%C3%9743


#20

I think j changes because first its mutable and we’re modifying/saving the same variable in the list, so when we update it in the loop, it still gets updated where it was used (which is expected), so something like this works

fun main(args: Array<String>) {
    val result = mutableListOf<() -> Unit>()
    var i = 0
    (0..5).forEach {
      i++
      val j = i
      result += { println("$k $it") }
    }
  result.forEach { it() }
}

This would give the same thing as the javascript example