Adrian
Adrian

Reputation: 3792

Range object throwing java.lang.StringIndexOutOfBoundsException

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

Answers (2)

Silvio Mayolo
Silvio Mayolo

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 from Int values to the element type of the sequence. The isDefinedAt method of a sequence returns true for the interval from 0 until length.

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

jwvh
jwvh

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

Related Questions