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



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

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

我正在使用Shapeless 2.3.1和Scala 2.11.8。在查找LabelledGeneric[Color]的隐式值方面,我看到了与我的测试和 REPL 不同的行为。(我实际上正在尝试自动派生其他类型类,但我也为此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$1@755f11d9

你所看到的null确实是带有和没有显式注释类型的隐式定义的语义的惊人结果。定义右侧的表达式LabelledGeneric[Color]是对apply方法对带有类型参数Colorobject LabelledGeneric的调用,它本身需要一个类型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
}

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

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

但 REPL 中的实际返回类型类似于

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

这是令人惊讶的,因为通常我们被鼓励为隐式放置类型注释。

相关内容

最新更新