La visione implicita non funziona: è colpa mia?
Ho dei problemi con una vista implicita. Sospetto che sia abbastanza banale e che possa avere una risposta imbarazzante. Ho una situazione come questa, insieme a (ovviamente, senza successo) tentativi di debug:
abstract class StoresNumeric[A, T: Numeric] {
def getNum(self: A): T
}
object StoresNumericSyntax {
implicit class StoresNumericOps[A, T: Numeric](value: A) {
def getNum(implicit sn: StoresNumeric[A, T]): T = sn.getNum(value)
}
}
case class ANumber[T: Numeric](
num: T
)
implicit def aNumberStoresNumeric[T: Numeric] =
new StoresNumeric[ANumber[T], T] {
def getNum(self: ANumber[T]): T = self.num
}
val a = ANumber[Int](3)
// 1. Works fine, so explicit conversion possible
aNumberStoresNumeric[Int].getNum(a)
// 2. Works fine, so implicit conversion possible
implicitly[StoresNumeric[ANumber[Int], Int]].getNum(a)
// 3. Doesn't work, so implicit conversion not working
println(implicitly[ANumber[Int] => StoresNumeric[ANumber[Int], Int]]) // no implicit view available...
// 4. The holy grail. Doesn't work, for the same reason as above, plus possibly other
a.getNum
Penso che il problema qui sia il mio implicit def, o più precisamente la mia mancanza di comprensione su cui implicit defeffettivamente dovrebbe funzionare. Non sono sicuro del ruolo svolto dai generici: il primo argomento generico dovrebbe essere usato per rappresentare il tipo che vorrei effettivamente convertire, o è OK così com'è?
Qualsiasi aiuto ricevuto con gratitudine.
Risposte
In primo luogo (# 3),
implicitly[ANumber[Int] => StoresNumeric[ANumber[Int], Int]]
è sbagliato. Non si definisce una conversione implicita da un tipo di dati a una classe di tipo, si definisce una conversione implicita da un tipo di dati a una classe implicita che introduce il metodo di estensione. Così dovrebbe essere
implicitly[ANumber[Int] => StoresNumericSyntax.StoresNumericOps[ANumber[Int], Int]]
e compila.
Siate preparati a implicitly[A => B]non verificare sempre l' esistenza di una conversione implicita da Aa B. *(vedi sotto)
In secondo luogo (# 4), quando si utilizza il metodo di estensione ( a.getNum) è necessario importare l'oggetto della sintassi:
import StoresNumericSyntax._
(a: StoresNumericOps[ANumber[Int], Int]).getNum
compila mentre
import StoresNumericSyntax._
a.getNum
produce (con scalacOptions += "-Xlog-implicits"acceso)
Warning:
StoresNumericOps is not a valid implicit value for App.a.type => ?{def getNum: ?} because:
ambiguous implicit values:
both object BigIntIsIntegral in object Numeric of type scala.math.Numeric.BigIntIsIntegral.type
and object IntIsIntegral in object Numeric of type scala.math.Numeric.IntIsIntegral.type
match expected type Numeric[T]
Se importi IntIsIntegral, aggiungerai questo implicito all'ambito locale (prima era solo nell'ambito implicito), quindi renderai la sua "priorità" "maggiore" di quella di BigIntIsIntegral. Provare
import StoresNumericSyntax._
import Numeric.IntIsIntegral
a.getNum
Si compila.
Scala 2.13.3.
Vedi anche come eseguire il debug degli impliciti (in fase di compilazione): In scala 2 o 3, è possibile eseguire il debug del processo di risoluzione implicita in runtime?
*Ad esempio, se modifichi la classe implicita come consigliato da @ LuisMiguelMejíaSuárez nei commenti
object StoresNumericSyntax {
implicit class StoresNumericOps[A](private val value: A) extends AnyVal {
def getNum[T: Numeric](implicit sn: StoresNumeric[A, T]): T = sn.getNum(value)
}
}
poi
import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]
non si compila
Warning:
StoresNumericOps is not a valid implicit value for ANumber[Int] => StoresNumericSyntax.StoresNumericOps[ANumber[Int]] because:
hasMatchingSymbol reported error: type mismatch;
found : StoresNumericSyntax.StoresNumericOps.type
required: ANumber[Int] => StoresNumericSyntax.StoresNumericOps[App393.ANumber[Int]]
mentre risolto manualmente
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]](new StoresNumericOps(_))
compila e
import StoresNumericSyntax._
a: StoresNumericOps[ANumber[Int]]
compila anche.
Ma se rimuovo extends AnyVal
object StoresNumericSyntax {
implicit class StoresNumericOps[A](private val value: A) /*extends AnyVal*/ {
def getNum[T: Numeric](implicit sn: StoresNumeric[A, T]): T = sn.getNum(value)
}
}
poi
import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]
compila.
Anche se divido la classe implicita in una classe + una conversione implicita
object StoresNumericSyntax {
/*implicit*/ class StoresNumericOps[A](private val value: A) extends AnyVal {
def getNum[T: Numeric](implicit sn: StoresNumeric[A, T]): T = sn.getNum(value)
}
implicit def toStoresNumericOps[A](value: A): StoresNumericOps[A] =
new StoresNumericOps(value)
}
poi
import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]
compila.
Perché implicitly[A => B]non è la stessa val x: B = ??? : Aspiegata qui:
In scala, ci sono condizioni in cui la vista implicita non sarà in grado di propagarsi ad altre funzioni implicite?
Quando si chiama una funzione scala con una macro in fase di compilazione, come eseguire il failover senza problemi quando causa errori di compilazione?
Scala Kleisli genera un errore in IntelliJ
Quali sono le regole nascoste riguardanti l'inferenza del tipo nella risoluzione delle conversioni implicite?
Scala: `valori impliciti ambigui` ma non viene trovato il valore corretto
L'impatto della presenza / assenza di extends AnyValsulla risoluzione implicita può essere un bug, ma la differenza tra istanza implicita ( implicitly[A => B]) e conversione implicita ( val x: B = ??? : A) è più o meno intenzionale (vengono utilizzate diverse strategie di inferenza di tipo e parametri di tipo risolutivi).