5
votes

If I define a simple stringToInt function and store it as a val, everything works as expected, e.g.

scala> def stringToInt1: (String => Int) = _.toInt
stringToInt1: String => Int

scala> stringToInt1("1")
res0: Int = 1

However, if I then make that implicit, it causes a stack overflow:

scala> implicit def stringToInt2: (String => Int) = _.toInt
stringToInt2: String => Int

scala> stringToInt2("1")
java.lang.StackOverflowError
at .stringToInt2(<console>:7)
at $anonfun$stringToInt2$1.apply(<console>:7)
at $anonfun$stringToInt2$1.apply(<console>:7)
...

At first I suspected that this was because the underscore wasn't resolving to what I expected, but that's not the case, as this style of implicit val works fine for the following simple function:

scala> implicit def plusTwo: (Int => Int) = _ + 2
plusTwo: Int => Int

scala> plusTwo(2)
res2: Int = 4

If I define the parameter explicitly, no stack overflow:

scala> implicit def stringToInt3(s: String) = s.toInt
stringToInt3: (s: String)Int

scala> stringToInt3("1")
res3: Int = 1

(If trying this yourself and this last case stack overflows, restart the scala console and redo this last step)

So my question is, why is the original implicit not correctly resolving?

Edit

Ok digging a little deeper here, it seems that the problem is with the implicit conversion from String to StringOps. If we cut that out, it works fine:

scala> import scala.collection.immutable.StringOps
import scala.collection.immutable.StringOps

scala> implicit def stringToInt4: (String => Int) = new StringOps(_).toInt
stringToInt4: String => Int

scala> stringToInt4("1")
res4: Int = 1

But why would that implicit conversion be causing the issue?

2
As noted in a comment below, I'm aware that you would typically use a val not a def in the first two cases - the reason I've used def throughout in this question is to highlight the fact that the stack overflow is prevented by switching to a method that takes a string and returns an int.Rheese

2 Answers

9
votes

Adding to the other replies.

There is no toInt method on String. Scala has to find an implicit conversion that will yield a type that has a toInt method.

Usually the StringOps conversion provides this toInt. However Int has a toInt too, so scala finds your conversion from String => Int and decides that it has precedence over the StringOps conversion, thus applying it recursively.

This is why StringToInt4 works, as you explicitly tell the compiler what conversion you want. Maybe you could write it as: implicit def stringToInt5: (StringOps => Int) = _.toInt or check how implicits are resolved and how one takes precedence over the other.

0
votes

Note what you are doing here:

scala> def stringToInt1: (String => Int) = _.toInt
stringToInt1: String => Int

You are defining a method (using def) that takes no parameters and returns a function from String to Int. Are you doing it this way on purpose, or is it just because you don't exactly understand the syntax?

You could have created a val instead:

val stringToInt1: (String => Int) = _.toInt

Now, stringToInt1 is a val that contains a function to convert String to Int.

With your stringToInt2, Scala is recursively applying the method. I don't know why it does that in the case of stringToInt2 but not with plusTwo.

Note that stringToInt3 is not the same thing as stringToInt1 and stringToInt2. It's a method that takes a String and returns an Int, not a method that takes no parameters and returns a function from String to Int.