Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala/Shapeless: Updating named field in case class instance

I am trying to create a type class that will allow me to increment an Int field called "counter" on any case class, as long as that class has such a field.

I have tried to do this with Shapeless but am hitting walls (after first trying to digest "The Type Astronaut's Guide to Shapeless", the "Feature overview" of Shapeless 2.0.0 and numerous threads on Stack Overflow).

What I want is to be able to do something like

case class MyModel(name:String, counter:Int) {}

val instance = MyModel("Joe", 4)
val incremented = instance.increment()
assert(incremented == MyModel("Joe", 5))

And it should work for any case class with a suitable counter field.

I thought that this would be possible using a type class and Shapeless' record abstraction (and an implicit conversion to get the increment functionality added as a method). The bare bones would be something like this:

trait Incrementer[T] {
  def inc(t:T): T
}

object Incrementer {
  import shapeless._ ; import syntax.singleton._ ; import record._

  implicit def getIncrementer[T](implicit generator: LabelledGeneric[T]): Incrementer[T] = new Incrementer[T] {
    def inc(t:T) = {
      val repr = generator.to(t)
      generator.from(repr.replace('counter, repr.get('counter) + 1))
    }
  }     
}

However, this does not compile. The error being value replace is not a member of generator.Repr. I guess this is because the compiler does not have any guarantee as to T having a field called counter and it being of type Int. But how could I tell it so? Is there better/more documentation on Shapeless' record? Or is this a completely wrong way to go?

like image 772
holbech Avatar asked Jun 21 '17 12:06

holbech


1 Answers

You have to just implicitly require a Modifier

import shapeless._
import ops.record._
implicit class Incrementer[T, L <: HList](t: T)(
  implicit gen: LabelledGeneric.Aux[T, L],
  modifier: Modifier.Aux[L, Witness.`'counter`.T, Int, Int, L]
) {
  def increment(): T = gen.from(modifier(gen.to(t), _ + 1))
}
like image 159
Federico Pellegatta Avatar answered Nov 15 '22 07:11

Federico Pellegatta