本文介绍了Shapeless 在测试中找不到隐式,但可以在 REPL 中找到的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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 中找到的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 10:37