St.Antario St.Antario - 22 days ago 7
Scala Question

Applying implicit conversion to map

I tried implicit conversions in the following example:

val m: Map[Int, Int] = Map(10 -> "asd") //fine
val mm: Map[Int, Int] = Map("asd" -> 20) //type mismatch; found: (String, Int)
//required: (Int, Int)

implicit def stringToInt(str: String): Int = 10


Why can't we apply implicit conversions to map keys? Is there a way to work around this?

Answer

It doesn't work because you're using -> which is an (inline) operator:

implicit final class ArrowAssoc[A](self : A) extends scala.AnyVal {
  @scala.inline
  def ->[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ }
  def →[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ }
}

You can see that by the time B is evaluated, A is already "fixed". Let's just say that you can only (implicitly) convert the right hand side of a tuple when using -> operator:

implicit def stringToInt(str: String): Int = 10  
implicit def intToStr(str: Int): String = "a"

val a: Map[Int, Int] = Map(10 -> "asd") //fine
val b: Map[Int, Int] = Map("asd" -> 20) // error! cant change left side

val c: Map[String, String] = Map("asd" -> 20) // fine 
val d: Map[String, String] = Map(10 -> "asd") // error! cant change left side

Because of similar compiler quirks related to using operator ->, @Jorg's solution works in one direction, but not the other:

implicit def tupleIntifier(t: (String, Int)) = (10, 10)
implicit def tupleIntifier2(t: (Int, String)) = (10, 10)

val a: Map[Int, Int] = Map("asd" -> 20)  // uses tupleIntifier
val b: Map[Int, Int] = Map(10 -> "asd") // fails!!

However, if you avoid using -> operator altogether and simply use (key, value) syntax, it will work:

val a: Map[Int, Int] = Map((10, "asd"))
val b: Map[Int, Int] = Map(("asd", 20))

implicit def stringToInt(str: String): Int = 15

println(a)  // prints Map(10 -> 15)
println(b) // prints Map(15 -> 20)
Comments