问题描述
我有一个看起来像这样的案例类:
I have a case class that looks like this:
case class Color(name: String, red: Int, green: Int, blue: Int)
我在 Scala 2.11.8 中使用 Shapeless 2.3.1.在查找 LabelledGeneric[Color]
的隐含值方面,我发现我的测试和 REPL 有不同的行为.(我实际上是在尝试自动派生一些其他类型类,但我也得到了 null
)
I'm using Shapeless 2.3.1 with Scala 2.11.8. I'm seeing different behavior from my test and the REPL in terms of finding the implicit value for LabelledGeneric[Color]
. (I'm actually trying to auto-derive some other typeclass, but I'm getting null
for that too)
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
}
}
}
此测试失败,因为 colorLabel
为 null
.为什么?
This test fails because colorLabel
is null
. Why?
从 REPL 中,我可以找到 LabelledGeneric[Color]
:
From the REPL, I can find 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]
是对带有类型参数的 object LabelledGeneric
上的 apply
方法的调用Color
本身需要一个 LabelledGeneric[Color]
类型的隐式参数.隐式查找规则意味着具有最高优先级的相应范围内隐式定义是当前正在定义的implicit val colorLabel
,即.我们有一个循环,最终得到默认的 null
初始值设定项.如果,OTOH,类型注释被省略,colorLabel
不在范围内,您将得到您期望的结果.这很不幸,因为正如您正确地观察到的那样,我们应该尽可能明确地注释隐式定义.
The null
you're seeing is, indeed, a surprising consequence of the semantics of implicit definitions with and without explicitly annotated types. The expression on the right hand side of the definition, LabelledGeneric[Color]
, is a call of the apply
method on object LabelledGeneric
with type argument Color
which itself requires an implicit argument of type LabelledGeneric[Color]
. The implicit lookup rules imply that the corresponding in-scope implicit definition with the highest priority is the implicit val colorLabel
which is currently under definition, ie. we have a cycle which ends up with the value getting the default null
initializer. If, OTOH, the type annotation is left off, colorLabel
isn't in scope, and you'll get the result that you expect. This is unfortunate because, as you rightly observe, we should explicitly annotate implicit definitions wherever possible.
shapeless 的 cachedImplicit
提供了一种解决这个问题的机制,但在描述它之前我需要指出一个额外的复杂性.LabelledGeneric[Color]
不是 colorLabel
的正确类型.LabelledGeneric
有一个类型成员 Repr
,它是您正在为其实例化 LabelledGeneric
的类型的表示类型,并通过注释定义您是否明确放弃了包括该内容的 LabelledGeneric[Color]
的细化.结果值将无用,因为它的类型不够精确.使用正确的类型注释隐式定义,无论是通过显式细化还是使用等效的 Aux
都很困难,因为表示类型很难显式写出,
shapeless's cachedImplicit
provides a mechanism for solving this problem, but before describing it I need to point out one additional complication. The type LabelledGeneric[Color]
isn't the right type for colorLabel
. LabelledGeneric
has a type member Repr
which is the representation type of the type you're instantiating the LabelledGeneric
for, and by annotating the definition as you have you are explicitly discarding the refinement of LabelledGeneric[Color]
which includes that. The resulting value would be useless because its type isn't sufficiently precise. Annotating the implicit definition with the correct type, either with an explicit refinement or using the equivalent Aux
is difficult because the representation type is complex to write out explicitly,
object CustomProtocol {
implicit val colorLabel: LabelledGeneric.Aux[Color, ???] = ...
}
同时解决这两个问题是一个两步过程,
Solving both of these problems simultaneously is a two step process,
- 获取具有完全优化类型的
LabelledGeneric
实例. - 使用显式注释定义缓存的隐式值,但不生成导致
null
的初始化循环.
- obtain the
LabelledGeneric
instance with the fully refined type. - define the cached implicit value with an explict annotation but without generating an init cycle resulting in a
null
.
最后看起来像这样,
object CustomProtocol {
val gen0 = cachedImplicit[LabelledGeneric[Color]]
implicit val colorLabel: LabelledGeneric.Aux[Color, gen0.Repr] = gen0
}
这篇关于Shapeless 在测试中找不到隐式,但可以在 REPL 中找到的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!