Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does foreach only execute once when param name isn't specified?

Tags:

loops

scala

I want to execute a function three times. That function happens to return a string.

def doThingReturnString(): String = {
  println("Did a thing, returned a string.")
  "abcdef"
}

(1 to 3).foreach { n =>
  doThingReturnString()
}

(1 to 3).foreach {
  doThingReturnString()
}

I expect both loops to print three lines. Instead, the first loop prints three lines and the second loop prints one.

Did a thing, returned a string.

Did a thing, returned a string.

Did a thing, returned a string.


Did a thing, returned a string.

Why does naming the parameter cause the loop to only execute once?

like image 713
crenshaw-dev Avatar asked Aug 19 '19 20:08

crenshaw-dev


2 Answers

foreach expects a function Int => U (where U can be "whatever"). Period. If you want to ignore the parameter, use an underscore.

(1 to 3).foreach { _ => doThingReturnString() }

When you write

(1 to 3).foreach { doThingReturnString() }

The braces act like parentheses

(1 to 3).foreach(doThingReturnString())

The argument for foreach must be Int => U, but here, it is a String. A String can be implicitly converted to an Int => U, because a String can implicitly convert to WrappedString, which treats it as a collection type, specifically as a Seq[Char], which can be upcast to a PartialFunction[Int, Char] from indices to elements, which can be upcast to Int => Char. Thus, you've essentially written

val temp = doThingReturnString()
(1 to 3).foreach { i => temp.charAt(i) }

The reason for this behavior is that treating Seq[A]s as PartialFunction[Int, A]s is pretty sensible. Also sensible is being able to treat strings like the other collection types, so we have an implicit conversion to augment Java's String with Scala's collection architecture. Putting them together, so that Strings turn into Int => Chars, produces somewhat surprising behavior.

like image 188
HTNW Avatar answered Nov 09 '22 01:11

HTNW


Let's change your expression to:

(1 to 3).foreach { "abc"}

Can you guess the result? It is

java.lang.StringIndexOutOfBoundsException: String index out of range: 3

If we change it to

(1 to 3).foreach { "abcd"}

the program executes without the exception. So, in case of your expression:

(1 to 3).foreach {
  doThingReturnString()
}

you: firstly execute doThingReturnString(), which returns a string "abcdef". Then, for each number i in the range 1 to 3, the compiler executes "abcdef"(i).

As to why (1 to 3).foreach { n => doThingReturnString() } is seemingly treated differently from (1 to 3).foreach { doThingReturnString() }, the best explanation I know comes from the book Scala Puzzlers (p. 20; no affiliation with the authors):

Since anonymous functions are often passed as arguments, it’s common to see them surrounded by { ... } in code. It’s easy to think that these curly braces represent an anonymous function, but instead they delimit a block expression: one or multiple statements, with the last determining the result of the block.

like image 44
lukeg Avatar answered Nov 09 '22 00:11

lukeg