Shapeless 在测试中找不到隐式,但可以在 REPL 中找到

Shapeless not finding implicits in test, but can in REPL

我有一个案例 class 看起来像这样:

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

我正在使用 Shapeless 2.3.1 和 Scala 2.11.8。在查找 LabelledGeneric[Color] 的隐式值方面,我从我的测试和 REPL 中看到了不同的行为。 (实际上我正在尝试自动派生一些其他类型 class,但我也得到 null

内测

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

此测试失败,因为 colorLabelnull。为什么?

REPL

从 REPL 中,我可以找到 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@755f11d9

我刚刚意识到我正在为隐式输入 return 类型:

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

但 REPL 中的实际 return 类型类似于

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

删除类型注释后测试通过:

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

这令人惊讶,因为通常我们被鼓励为隐式添加类型注释。

您所看到的 null 确实是带有和不带有显式注释类型的隐式定义语义的令人惊讶的结果。定义右侧的表达式 LabelledGeneric[Color] 是在 object LabelledGeneric 上使用类型参数 Color 调用 apply 方法,它本身需要类型为隐式参数LabelledGeneric[Color]。隐式查找规则意味着具有最高优先级的相应范围内隐式定义是当前正在定义的 implicit val colorLabel ,即。我们有一个循环,它以获取默认 null 初始值设定项的值结束。如果,OTOH,类型注释被关闭, colorLabel 不在范围内,您将得到您期望的结果。这很不幸,因为正如您正确观察到的那样,我们应该尽可能明确地注释隐式定义。

shapeless 的 cachedImplicit 提供了解决这个问题的机制,但在描述它之前我需要指出一个额外的复杂性。类型 LabelledGeneric[Color] 不是 colorLabel 的正确类型。 LabelledGeneric 有一个类型成员 Repr ,它是您要为其实例化 LabelledGeneric 的类型的表示类型,并且通过注释您所拥有的定义,您明确地放弃了 LabelledGeneric[Color] 其中包括那个。结果值将毫无用处,因为它的类型不够精确。用正确的类型注释隐式定义,无论是显式细化还是使用等价物 Aux 都很困难,因为表示类型很难显式写出,

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

同时解决这两个问题需要两个步骤,

  • 获取 LabelledGeneric 具有完全优化类型的实例。
  • 使用显式注释定义缓存的隐式值,但不生成导致 null.
  • 的初始化循环

最终看起来像这样,

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