La visione implicita non funziona: è colpa mia?

Sep 19 2020

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

2 DmytroMitin Sep 19 2020 at 15:00

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).