Reputation: 3792
In the code below, the first line compiles and executes with no result, as expected. The second one compiles as well (it should not!) and throws StringIndexOutOfBoundsException. Why?
scala> 3 to 4 foreach (_ => "a")
scala> 3 to 4 foreach ("a")
java.lang.StringIndexOutOfBoundsException: String index out of range: 3
at java.lang.String.charAt(String.java:658)
at scala.collection.immutable.StringLike.apply(StringLike.scala:56)
at scala.collection.immutable.StringLike.apply$(StringLike.scala:56)
at scala.collection.immutable.WrappedString.apply(WrappedString.scala:34)
at scala.collection.immutable.WrappedString.apply(WrappedString.scala:34)
at scala.collection.immutable.Range.foreach(Range.scala:158)
... 28 elided
Upvotes: 1
Views: 81
Reputation: 70277
In your code snippet, "a"
is actually a function. Don't believe me? Let's go down the rabbit hole together.
"a"
is a java.lang.String
. From a Scala perspective, that type is... pretty boring. So Scala has an implicit conversion to the WrappedString
type, which your code is calling. Now, if we follow the trail of parents, we see that
WrappedString <: IndexedSeq[Char] <: immutable.Seq[Char] <: Seq[Char]
<: PartialFunction[Int, Char] <: (Int) => Char
So WrappedString
is actually a subclass of (Int) => Char
. We can see the rationale for this in the docs for Seq
Another way to see a sequence is as a
PartialFunction
fromInt
values to the element type of the sequence. TheisDefinedAt
method of a sequence returns true for the interval from0
untillength
.
So a string (and in fact, any sequence) can behave like a function which takes a single integer index and returns that position in the sequence. So when you try to get the third (and later fourth) position in the string, you get an error.
As a general piece of advice, I deciphered this using a very handy compiler option. If you put the code you're asking about in a file wrapped in a minimal singleton, like so
object A {
3 to 4 foreach ("a")
}
Then "compile" it with scalac -print filename.scala
. You'll see something like this output
[[syntax trees at end of cleanup]] // test.scala
package <empty> {
object A extends Object {
def <init>(): A.type = {
A.super.<init>();
RichInt.this.to$extension0(scala.this.Predef.intWrapper(3), 4).foreach(scala.this.Predef.wrapString("a"));
()
}
}
}
The important line is just after the super
call.
RichInt.this.to$extension0(scala.this.Predef.intWrapper(3), 4).foreach(scala.this.Predef.wrapString("a"));
We see some weird magic happening to get the 3 to 4
syntax to behave, and then later on on the line we see scala.this.Predef.wrapString
, which indicates that the wrapString
conversion I linked to earlier is being called implicitly here. From there, it's just a matter of looking at the superclasses until you find what you're looking for.
Upvotes: 2
Reputation: 51271
Because the string "a"
doesn't have any elements at index 3
or index 4
. Change the string to "abcde"
and it won't throw that error. Change the foreach
to map
to see the results.
Upvotes: 2