Shapeless not finding implicits in test, but can in REPL

落爺英雄遲暮 提交于 2019-12-03 08:49:56

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'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,

  • 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.

That ends up looking like this,

object CustomProtocol {
  val gen0 = cachedImplicit[LabelledGeneric[Color]]
  implicit val colorLabel: LabelledGeneric.Aux[Color, gen0.Repr] = gen0
}

I just realized that I was putting return type for the implicit:

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

but the actual return type in the REPL is something like

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

The test passes when I remove the type annotation:

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

This is surprising, since normally we are encouraged to put type annotation for the implicits.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!