【问题标题】:Shapeless not finding implicits in test, but can in REPLShapeless 在测试中没有发现隐含,但在 REPL 中可以
【发布时间】:2016-05-21 19:58:18
【问题描述】:

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

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

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    您看到的null 实际上是带有和不带有显式注释类型的隐式定义的语义令人惊讶的结果。定义右侧的表达式LabelledGeneric[Color] 是对object LabelledGenericapply 方法的调用,其类型参数为Color,它本身需要一个类型为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
    }
    

    【讨论】:

      【解决方案2】:

      我刚刚意识到我正在为隐式设置返回类型:

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

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

      【讨论】:

        猜你喜欢
        • 2015-03-28
        • 2021-03-24
        • 2016-03-24
        • 1970-01-01
        • 2019-03-12
        • 2013-08-11
        • 1970-01-01
        • 1970-01-01
        • 2014-12-25
        相关资源
        最近更新 更多