What the code should do
The starting code has an array nums holding 1, 2, 3, 4, 5. Every even number should go up by one and every odd number should go down by one, so afterwards nums holds 0, 3, 2, 5, 4.
val nums = Array(1, 2, 3, 4, 5)
for (num <- nums) {
if (num % 2 == 0) {
num += 1
} else {
num -= 1
}
}
This code doesn't even compile. The compiler reports an error on each of the two changing lines:
value += is not a member of Int
value -= is not a member of Int
Why it fails
Two separate problems are hiding in these lines.
Problem 1: the loop variable is a value, not a variable
In for (num <- nums), Scala creates num as an immutable value, like a val, and sets it to the current element on each pass. You can't assign to a val. The statement num += 1 is shorthand for num = num + 1, and the compiler only accepts that shorthand when the left side is a var. Since num isn't, it looks for a method named += on Int, doesn't find one, and reports that += is not a member of Int.
Problem 2: a copy would not reach the array anyway
Suppose the compiler let you change num. It would still be the wrong thing to change. The loop variable holds a copy of the element's value. Changing a copy leaves the array alone. You can see this by copying into a var, which does compile:
val nums = Array(1, 2, 3, 4, 5)
for (num <- nums) {
var copy = num
copy += 1
}
println(nums.mkString(", "))
1, 2, 3, 4, 5
The incremented copy disappears at the end of each pass, and nums is unchanged. Making num mutable would remove the first error and leave you with a program that compiles but does nothing useful.
The fix: loop over positions
An array element can be changed through its position. nums(i) = 10 stores 10 at index i, and nums(i) += 1 reads the element, adds one and stores the result back at the same index. So the loop has to produce indices instead of values, and the body works on nums(i).
- Loop over the indices. Replace
num <- numswithi <- 0 until nums.size. The range0 until nums.sizecovers 0 up to the last valid index,nums.size - 1, and leaves out the end value. - Read through the index. The test becomes
nums(i) % 2 == 0. Scala indexes arrays with parentheses. - Write through the index. The branches become
nums(i) += 1andnums(i) -= 1. This works on an array element even thoughnumsis declared withval:valfixes which array the name refers to, not what the array contains.
The solution in Scala
val nums = Array(1, 2, 3, 4, 5)
for (i <- 0 until nums.size) {
if (nums(i) % 2 == 0) {
nums(i) += 1
} else {
nums(i) -= 1
}
}
After the loop, nums holds 0, 3, 2, 5, 4: 1 became 0, 2 became 3, 3 became 2, 4 became 5 and 5 became 4.
The same loop can also be written with nums.indices, which is the same range as 0 until nums.size. Either form is fine.
Gotchas
valversusvaron the array.val nums = Array(...)is fine for this task. An array is mutable and fixed in size, so you can replace its elements but you can't add or remove any. Onlynums = Array(7, 8)would be rejected, because that reassigns the name.- Looping over elements still helps for reading. If you only need to look at the values, as when summing them or printing them,
for (num <- nums)is the cleaner form. Use indices when you need to store something back. - Immutable collections behave differently. A
Listcan't be changed in place at all. To get a changed list you build a new one, for example withmap:List(1, 2, 3).map(_ + 1)returnsList(2, 3, 4)and leaves the original list as it was. - Going out of range. An index of
nums.sizeor larger throws anArrayIndexOutOfBoundsException. Ending the range atnums.sizewithuntilavoids it, whereas0 to nums.sizewould include that bad index.
Key takeaways
- The loop variable in
for (x <- array)is an immutable copy of an element. - To change the elements of an array, loop over its indices and assign to
array(i). +=on anIntonly compiles when the target is avaror an array slot.
