crenshaw-dev
crenshaw-dev

Reputation: 8354

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

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?

Upvotes: 6

Views: 267

Answers (3)

Volty De Qua
Volty De Qua

Reputation: 290

Consider this:

object Intercept { def apply[T](v: T) { println(s"Intercept called with value '$v'") } }
(1 to 3).foreach { Intercept[Int] } // or just (1 to 3) foreach Intercept[Int]

, that outputs:

Intercept called with value '1'
Intercept called with value '2'
Intercept called with value '3'

And consider this

(1 to 3).foreach { println("start"); println }

that prints

start
1
2
3

vs

(1 to 3).foreach { x => println("start"); println(x) }

that prints

start
1
start
2
start
3

So,

  • if anonymous function is provided (elem =>), everything gets executed
  • if block is provided, then foreach acts on the result of the block (the last line) invoking 'apply(_)' on it, with the exception where _ is used.

Upvotes: 0

lukeg
lukeg

Reputation: 4399

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.

Upvotes: 4

HTNW
HTNW

Reputation: 29193

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.

Upvotes: 12

Related Questions