没有形状在测试中没有发现隐含,但可以在REPL中找到

时间:2016-05-21 19:58:19

标签: scala shapeless

我有一个如下所示的案例类:

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

我在Scala 2.11.8中使用了Shapeless 2.3.1。我在查找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

2 个答案:

答案 0 :(得分:4)

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

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

LabelledGeneric[Color]

同时解决这两个问题是一个两步过程,

  • 获取具有完全细化类型的Aux实例。
  • 使用明确的注释定义缓存的隐式值,但不生成导致object CustomProtocol { implicit val colorLabel: LabelledGeneric.Aux[Color, ???] = ... } 的初始化循环。

最终看起来像这样,

LabelledGeneric

答案 1 :(得分:3)

我刚刚意识到我正在为隐含的

添加返回类型
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]
}

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