Применение неявного преобразования к карте

Я попробовал неявные преобразования в следующем примере:

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

Почему мы не можем применять неявные преобразования к ключам карты? Есть ли способ обойти это?


person St.Antario    schedule 11.11.2016    source источник


Ответы (3)


Это не работает, потому что вы используете ->, который является (встроенным) оператором:

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 */ }
}

Вы можете видеть, что к тому времени, когда B оценивается, A уже «исправлено». Скажем так, вы можете (неявно) преобразовать правую часть кортежа только при использовании оператора ->:

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

Из-за подобных причуд компилятора, связанных с использованием оператора ->, решение @Jorg работает в одном направлении, но не в другом:

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!!

Однако, если вы вообще избегаете использования оператора -> и просто используете синтаксис (key, value), он будет работать:

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)
person slouc    schedule 11.11.2016

Пожалуйста, посмотрите на сообщение об ошибке, которое вы получаете:

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

Сообщение об ошибке не касается String вместо Int, оно касается (String, Int) вместо (Int, Int). Итак, вы просто конвертируете не ту вещь:

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)

Вуаля! Оно работает.

person Jörg W Mittag    schedule 11.11.2016
comment
Круто, большое спасибо. Но почему тогда первые примеры работают? - person St.Antario; 11.11.2016

Если бы вы добавили такое общее неявное преобразование, вы бы потеряли безопасность типов, которую обеспечивает Scala, потому что любая String стала бы Int по мере необходимости, где угодно, без вмешательства программиста. На самом деле, когда вы хотите создать эту карту из других данных, вы, вероятно, уже знаете типы данных этих других данных. Итак, если известно, что ключи являются целыми числами, преобразуйте их в Int и используйте их таким образом. В противном случае используйте строки. Ваш пример очень искусственный. Какую конкретную проблему вы пытаетесь решить?

person radumanolescu    schedule 11.11.2016
comment
На самом деле проблема довольно скучная, это написание тестов с помощью scalatest. Мне нужно преобразование для удобства чтения. - person St.Antario; 11.11.2016