Visão implícita não funciona - minha def implícita é a culpada?

Sep 19 2020

Estou tendo problemas com uma visão implícita. Suspeito que isso seja bastante trivial e possa ter uma resposta embaraçosamente fácil. Tenho uma situação como esta, juntamente com tentativas (obviamente malsucedidas) de depurá-la:

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

Acho que o problema aqui é meu implicit defou, mais precisamente, minha falta de compreensão sobre o que implicit defrealmente deveria funcionar. Não tenho certeza de qual papel os genéricos desempenham - o primeiro argumento genérico deve ser usado para representar o tipo que eu realmente gostaria que fosse convertido ou está OK como está?

Qualquer ajuda recebida com gratidão.

Respostas

2 DmytroMitin Sep 19 2020 at 15:00

Em primeiro lugar (# 3),

implicitly[ANumber[Int] => StoresNumeric[ANumber[Int], Int]]

está errado. Você não define uma conversão implícita de um tipo de dados para uma classe de tipo, você define uma conversão implícita de um tipo de dados para um método de extensão de introdução de classe implícita. Então deveria ser

implicitly[ANumber[Int] => StoresNumericSyntax.StoresNumericOps[ANumber[Int], Int]]

e ele compila.

Esteja preparado para que implicitly[A => B]nem sempre verifique Ase Bexiste uma conversão implícita de para . *(ver abaixo)

Em segundo lugar (# 4), quando você usa o método de extensão ( a.getNum), você deve importar o objeto de sintaxe:

import StoresNumericSyntax._
(a: StoresNumericOps[ANumber[Int], Int]).getNum

compila enquanto

import StoresNumericSyntax._
a.getNum

produz (com scalacOptions += "-Xlog-implicits"ligado)

 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 você importar, IntIsIntegralvocê adicionará isso implícito ao escopo local (antes disso, ele estava apenas no escopo implícito), portanto, tornará sua "prioridade" "mais alta" do que a de BigIntIsIntegral. Experimentar

import StoresNumericSyntax._
import Numeric.IntIsIntegral
a.getNum

Ele compila.

Scala 2.13.3.

Veja também como depurar implícitos (em tempo de compilação): No scala 2 ou 3, é possível depurar processos de resolução implícita em tempo de execução?


*Por exemplo, se você modificar a classe implícita como @ LuisMiguelMejíaSuárez informado nos comentários

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

então

import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]

não 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]]

enquanto resolvido manualmente

implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]](new StoresNumericOps(_)) 

compila e

import StoresNumericSyntax._
a: StoresNumericOps[ANumber[Int]]

compila também.

Mas se eu remover 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)
  }
}

então

import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]

compila.

Além disso, se eu dividir a classe implícita em uma classe + uma conversão implícita

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

então

import StoresNumericSyntax._
implicitly[ANumber[Int] => StoresNumericOps[ANumber[Int]]]

compila.

Por que implicitly[A => B]não é o mesmo que val x: B = ??? : Aestá explicado aqui:

Em scala, há alguma condição em que a visualização implícita não seja capaz de se propagar para outra função implícita?

Ao chamar uma função scala com macro de tempo de compilação, como fazer o failover sem problemas quando isso causa erros de compilação?

Scala Kleisli gera um erro no IntelliJ

Quais são as regras ocultas em relação à inferência de tipo na resolução de conversões implícitas?

Escala: `valores implícitos ambíguos`, mas o valor correto não foi encontrado

O impacto da presença / ausência de extends AnyValna resolução implícita pode ser um bug, mas a diferença entre instância implícita ( implicitly[A => B]) e conversão implícita ( val x: B = ??? : A) é mais ou menos intencional (estratégias diferentes de inferência de tipo, parâmetros de tipo de resolução são usados).