Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why implicit conversion doesn't work in Lists?

Could someone give a quick explanation why implicit conversion doesn't work in these cases? Thanks.

scala> implicit def strTwoInt (s: String):Int = s.toCharArray.map{_.asDigit}.sum
strTwoInt: (s: String)Int

scala> List[Int]("1","2","3") sum
res3: Int = 6

scala> List("1","2","3") sum
<console>:9: error: could not find implicit value for parameter num: Numeric[java.lang.String]
        List("1","2","3") sum

scala> val a = List("1","2","3")                          
scala> a.foldLeft(0)((i:Int, j:Int) => i+j)
<console>:10: error: type mismatch;
 found   : (Int, Int) => Int
 required: (Int, java.lang.String) => Int
like image 979
Luigi Plinge Avatar asked Jul 18 '11 06:07

Luigi Plinge


2 Answers

Your implicit conversion converts a String in an Int. In your first example, it is triggered by the fact that you try to put Strings into a List of Ints.

In your second example, you have a List of String and you call the method sum, which takes an implicit Numeric[String]. Your conversion does not apply because you neither try to pass a String somewhere the compiler was expecting an Int, nor you tried to call a method which is defined in Int and not in String. In that case, you can either define a Numeric[String] which use explicitly your conversion, or use a method which takes a List[Int] as parameter (giving hint to the compiler):

scala> def sumIt( xs: List[Int] ) = xs sum
sumIt: (xs: List[Int])Int

scala> sumIt( List("1","2","3") )
res5: Int = 6

In the third example, the foldLeft second argument must be of type:

(Int,String) => Int

the one you actually passed is of type:

(Int,Int) => Int

however, you did not define any implicit conversion between these two types. But:

a.foldLeft(0)((i:Int, j:String) => i+j)

triggers your conversion and works.

Edit: Here's how to implement the Numeric[String]:

implicit object StringNumeric extends math.Numeric[String] {
  val num = math.Numeric.IntIsIntegral
  def plus(x: String, y: String) = num.plus(x,y).toString
  def minus(x: String, y: String) = num.minus(x,y).toString
  def times(x: String, y: String) = num.times(x,y).toString
  def negate(x: String): String = num.negate(x).toString
  def fromInt(x: Int) = x.toString
  def toInt(x: String) = x
  def toLong(x: String) = toInt(x)
  def toFloat(x: String) = toInt(x)
  def toDouble(x: String) = toInt(x)
  def compare(x:String,y:String) = num.compare(x,y)
}

scala> List("1","2","3") sum
res1: java.lang.String = 6

It works, but the result is a String.

like image 151
paradigmatic Avatar answered Oct 16 '22 06:10

paradigmatic


Here's a quick explanation: implicit conversions apply to the types the convert from and to directly (e.g., String and Int in your case), and not to any parametrized type T[String] or T[Int] — unless implicit conversions have been defined for T itself, and it is not the case for lists.

Your implicit conversion does not apply in your two cases (and even if you had an implicit conversion from List[String] to List[Int], it wouldn't apply). It would automatically applied only when you need a value of type Int and you're passing String instead. Here, in the first case, the method sum asks for a Numeric[String] implicit parameter — an implicit conversion from String to Int does not come into play here.

Similar problem for your next attempt: the foldLeft on your collection requires a function of type (Int, String) => Int. Imagine the mess we would get into if, based on an implicit conversion from String to Int, the compiler automatically provided an implicit conversion from (Int, Int) => Int to (Int, String) => Int

For all these cases, the easy way to fix it is to explicitly call .map(stringToInt) on your collection beforehand.

like image 43
Jean-Philippe Pellet Avatar answered Oct 16 '22 08:10

Jean-Philippe Pellet