Lifeless does not detect implications in the test, but may in REPL

I have a case class that looks like this:

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

I am using Shapeless 2.3.1 with Scala 2.11.8. I see different behavior from my test and REPL in terms of finding an implicit value for LabelledGeneric[Color] . (I'm actually trying to automatically output some other classes, but I get null too)

Internal test

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

This test failed because colorLabel is null . Why?

REPL

From 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 
+5
source share
2 answers

null you see is indeed an unexpected consequence of the semantics of implicit definitions with explicit annotated types. The expression on the right side of the LabelledGeneric[Color] definition is a call to the apply method on the object LabelledGeneric with an argument of type Color , which itself requires an implicit argument of type LabelledGeneric[Color] . Implicit search rules imply that the corresponding implicit definition in scope with the highest priority is implicit val colorLabel , which is currently under the definition, i.e. we have a loop that ends with a value receiving the default initializer null . If, OTOH, the annotation of the type is invalidated, colorLabel not in scope and you will get the expected result. This is unfortunate because, as you rightly pointed out, we must explicitly annotate implicit definitions where possible.

shapeless cachedImplicit provides a mechanism to solve this problem, but before describing it I need to point out another complication. The type LabelledGeneric[Color] not suitable for colorLabel . LabelledGeneric has a member of type Repr , which is the type representation of the type that you create for LabelledGeneric for, and annotating the definition, since you explicitly discard the LabelledGeneric[Color] that includes this. The resulting value would be useless, because its type is not accurate enough. Annotating an implicit definition with the correct type, either with explicit refinement, or using equivalent Aux , is difficult because the type of representation is difficult to write explicitly,

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

Solving both of these problems is simultaneously a two-step process,

  • get a LabelledGeneric instance with a fully refined type.
  • defines a cached implicit value using pronounced annotation, but without generating an initialization loop leading to null .

It looks like this:

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

I just realized that I am returning a type of type for implicit:

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

but the actual return type in REPL is similar to

 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 delete the annotation type:

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

This is surprising, since we are usually invited to put a type annotation for implications.

+3
source

All Articles