2

I'm trying to use discriminators in existing project and something is wrong with my classes I guess.

Consider this scodec example. If I change TurnLeft and its codec to

sealed class TurnLeft(degrees: Int) extends Command {
  def getDegrees: Int = degrees
}
implicit val leftCodec: Codec[TurnLeft] = uint8or16.xmap[TurnLeft](v => new TurnLeft(v), _.getDegrees)

I get

Error:(x, x) could not find Lazy implicit value of type scodec.Codec[Command]
    val codec: Codec[Either[UnrecognizedCommand, Command]] = discriminatorFallback(unrecognizedCodec, Codec[Command])

It all works if I make degrees field value field. I suspect it's something tricky with shapeless. What should I do to make it work ?

Sample project that demonstrates the issue is here.

expert
  • 29,290
  • 30
  • 110
  • 214

1 Answers1

1

shapeless's Generic is defined for "case-class-like" types. To a first approximation, a case-class-like type is one whose values can be deconstructed to it's constructor parameters which can then be used to reconstruct an equal value, ie.

case class Foo ...
val foo = Foo(...)
val fooGen = Generic[Foo]
assert(fooGen.from(fooGen.to(foo)) == foo)

Case classes with a single constructor parameter list meet this criterion, whereas classes which don't have public (lazy) vals for their constructor parameters, or a companion with a matching apply/unapply, do not.

The implementation of Generic is fairly permissive, and will treat (lazy) val members which correspond to constructor parameters (by type and order) as being equivalent to accessible constructor arguments, so the closest to your example that we can get would be something like this,

sealed class TurnLeft(degrees: Int) extends Command {
  val getDegrees: Int = degrees
}

scala> Generic[TurnLeft]
res0: shapeless.Generic[TurnLeft]{type Repr = Int :: HNil } = ...

In this case getDegrees is treated as the accessor for the single Int constructor parameter.

Miles Sabin
  • 23,015
  • 6
  • 61
  • 95
  • Awesome! Thanks, Miles. Side question about this. Will definition of these vals double memory footprint of given class? – expert Feb 06 '17 at 10:24
  • There is only one val here: the constructor argument of a non-case class doesn't generate a val unless it's explicitly marked as such. – Miles Sabin Feb 06 '17 at 10:32
  • I see. I assumed it still has to be stored somewhere if `def getDegrees` can return it. – expert Feb 06 '17 at 10:38