St.Antario
St.Antario

Reputation: 27375

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?

Upvotes: 4

Views: 1080

Answers (3)

slouc
slouc

Reputation: 9698

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)

Upvotes: 5

Jörg W Mittag
Jörg W Mittag

Reputation: 369420

Please, look at the error message you are getting:

error: type mismatch;
found   : (String, Int)
required: (Int, Int)
      val mm: Map[Int, Int] = Map("asd" -> 20)
                                        ^

The error message is not about String instead of Int, it is about (String, Int) instead of (Int, Int). So, you are simply converting the wrong thing:

implicit def tupleIntifier[T](t: (String, T)) = (10, t._2)

val mm: Map[Int, Int] = Map("asd" -> 20)
//=> mm: Map[Int,Int] = Map(10 -> 20)

Voila! It works.

Upvotes: 2

radumanolescu
radumanolescu

Reputation: 4161

If you were to add such a general implicit conversion, you would lose the type-safety that Scala is enforcing, because any String would become an Int as needed, anywhere, without intervention from the programmer. In reality, when you want to create that map from other data, you probably already know the data types of that other data. So if the keys are known to be integers, convert them to Int and use them like that. Otherwise, use strings. Your example is highly artificial. Which concrete problem are you trying to solve?

Upvotes: 1

Related Questions