Pregunta Shapeless no encuentra implicaciones en la prueba, pero puede en REPL


Tengo una clase de caso que se ve así:

case class Color(name: String, red: Int, green: Int, blue: Int)

Estoy usando Shapeless 2.3.1 con Scala 2.11.8. Estoy viendo un comportamiento diferente de mi prueba y el REPL en términos de encontrar el valor implícito para LabelledGeneric[Color]. (De hecho, estoy intentando auto-derivar algún otro tipo de clase, pero estoy obteniendo null para eso tambien

Prueba interna

package foo

import shapeless._
import org.specs2.mutable._

case class Color(name: String, red: Int, green: Int, blue: Int)

object CustomProtocol {
  implicit val colorLabel: LabelledGeneric[Color] = LabelledGeneric[Color]
}

class GenericFormatsSpec extends Specification {
  val color = Color("CadetBlue", 95, 158, 160)

  "The case class example" should {
    "behave as expected" in {
      import CustomProtocol._
      assert(colorLabel != null, "colorLabel is null")
      1 mustEqual 1
    }
  }
}

Esta prueba falla porque colorLabel es null. ¿Por qué?

REPL

Desde el REPL, puedo encontrar LabelledGeneric[Color]:

scala> case class Color(name: String, red: Int, green: Int, blue: Int)
defined class Color

scala> import shapeless._
import shapeless._

scala> LabelledGeneric[Color]
res0: shapeless.LabelledGeneric[Color]{type Repr = shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("name")],String],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("red")],Int],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("green")],Int],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("blue")],Int],shapeless.HNil]]]]} = shapeless.LabelledGeneric$$anon$1@755f11d9

5
2018-05-21 19:58


origen


Respuestas:


los null lo que estás viendo es, de hecho, una consecuencia sorprendente de la semántica de las definiciones implícitas con y sin tipos explícitamente anotados. La expresión en el lado derecho de la definición, LabelledGeneric[Color], es una llamada del apply método en object LabelledGeneric con argumento de tipo Color el cual a su vez requiere un argumento implícito de tipo LabelledGeneric[Color]. Las reglas de búsqueda implícita implican que la definición implícita correspondiente dentro del alcance con la prioridad más alta es la implicit val colorLabel que actualmente está bajo definición, es decir. tenemos un ciclo que termina con el valor de obtener el valor predeterminado null inicializador. Si, OTOH, se deja la anotación de tipo, colorLabel no está dentro del alcance, y obtendrá el resultado que espera. Esto es desafortunado porque, como bien observa, deberíamos anotar explícitamente las definiciones implícitas siempre que sea posible.

sin forma cachedImplicit proporciona un mecanismo para resolver este problema, pero antes de describirlo, necesito señalar una complicación adicional. El tipo LabelledGeneric[Color]  no es el tipo correcto para colorLabel. LabelledGeneric tiene un tipo de miembro Repr que es el tipo de representación del tipo que está creando la instancia LabelledGeneric para, y al anotar la definición que tienes, estás descartando explícitamente el refinamiento de LabelledGeneric[Color] que incluye eso. El valor resultante sería inútil porque su tipo no es lo suficientemente preciso. Anotar la definición implícita con el tipo correcto, ya sea con un refinamiento explícito o utilizando el equivalente Aux es difícil porque el tipo de representación es complejo de escribir explícitamente,

object CustomProtocol {
  implicit val colorLabel: LabelledGeneric.Aux[Color, ???] = ...
}

Resolver ambos problemas simultáneamente es un proceso de dos pasos,

  • obtener el LabelledGeneric instancia con el tipo totalmente refinado.
  • defina el valor implícito en caché con una anotación explícita pero sin generar un ciclo de inicio que dé como resultado un null.

Eso termina pareciéndose a esto,

object CustomProtocol {
  val gen0 = cachedImplicit[LabelledGeneric[Color]]
  implicit val colorLabel: LabelledGeneric.Aux[Color, gen0.Repr] = gen0
}

4
2018-05-22 08:31



Acabo de darme cuenta de que estaba poniendo el tipo de retorno para el implícito:

object CustomProtocol {
  implicit val colorLabel: LabelledGeneric[Color] = LabelledGeneric[Color]
}

pero el tipo de retorno real en el REPL es algo así como

shapeless.LabelledGeneric[Color]{type Repr = shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("name")],String],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("red")],Int],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("green")],Int],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("blue")],Int],shapeless.HNil]]]]}

La prueba pasa cuando elimino la anotación de tipo:

object CustomProtocol {
  implicit val colorLabel = LabelledGeneric[Color]
}

Esto es sorprendente, ya que normalmente se nos recomienda colocar anotaciones de tipo para las implicaciones.


3
2018-05-21 20:16