使用无形更新案例类字段

时间:2017-09-18 13:11:24

标签: scala shapeless

我定义了以下类型:

sealed trait Refreshable {
  val expireAt: Instant
  val version: Int
}

case class A(id: Int, expireAt: Instant, version: Int) extends Refreshable

case class B(name: String, expireAt: Instant, version: Int) extends Refreshable

我想定义一个方法refresh来更新这些类型的两个字段,但我找不到我需要的含义。看起来我应该能够用无形的方式做这样的事情吗?

def refresh[T <: RefreshableClass])(v: T)(implicit ev: LabelledGeneric[T]) = {
  val gen = ev.from(v)

  gen.updateWith('expire)(_.plus(...)).updateWith('version)(_+1)
}

2 个答案:

答案 0 :(得分:2)

密封您的特征。

sealed trait Refreshable {
  val expireAt: Instant
  val version: Int
}

使用Typelevel Scala以下变体有效:

def refresh[T <: Refreshable, R <: HList](v: T)(implicit
  generic: LabelledGeneric.Aux[T, R],
  modifier: Modifier.Aux[R, Symbol @@ "version", Int, Int, R],
  modifier1: Modifier.Aux[R, Symbol @@ "expireAt", Instant, Instant, R]): R = {
  val rec = generic.to(v)
  val rec1 = modifier(rec, _ + 1)
  modifier1(rec1, _.plus(1L, ChronoUnit.MILLIS))
}

使用Lightbend Scala(即标准Scala)

def refresh[T <: Refreshable, R <: HList](v: T)(implicit
  generic: LabelledGeneric.Aux[T, R],
  modifier: Modifier.Aux[R, Witness.`'version`.T, Int, Int, R],
  modifier1: Modifier.Aux[R, Witness.`'expireAt`.T, Instant, Instant, R]): R = {
  val rec = generic.to(v)
  val rec1 = modifier(rec, _ + 1)
  modifier1(rec1, _.plus(1L, ChronoUnit.MILLIS))
}

如果您更喜欢使用updateWith,也可以这样做:

def refresh[T <: Refreshable, R <: HList](v: T)(implicit
  generic: LabelledGeneric.Aux[T, R],
  modifier: Modifier.Aux[R, Witness.`'version`.T, Int, Int, R],
  modifier1: Modifier.Aux[R, Witness.`'expireAt`.T, Instant, Instant, R],
  selector: Selector.Aux[R, Witness.`'version`.T, Int],
  selector1: Selector.Aux[R, Witness.`'expireAt`.T, Instant]): R = {
  val rec = generic.to(v)
  rec.updateWith('expireAt)(_.plus(1L, ChronoUnit.MILLIS))
    .updateWith('version)(_ + 1)
}

通常,您不需要指定类型参数TR,应该推断出它们:

refresh(A(1, Instant.now, 1)) //1 :: 2017-09-20T18:53:34.039Z :: 2 :: HNil
refresh(B("a", Instant.now, 1)) // a :: 2017-09-20T18:53:34.074Z :: 2 :: HNil

但如果你确实需要,你可以指定它们:

refresh[A, Record.`'id -> Int, 'expireAt -> Instant, 'version -> Int`.T](A(1, Instant.now, 1))
refresh[B, Record.`'name -> String, 'expireAt -> Instant, 'version -> Int`.T](B("a", Instant.now, 1))

答案 1 :(得分:2)

可能最简单的方法是使用无定形镜片。首先,一些进口:

import java.time._
import shapeless._

如果字段匹配,则不需要继承普通特征,因此为了本示例的目的,我将删除它:

case class A(id: Int, expireAt: Instant, version: Int)
case class B(name: String, expireAt: Instant, version: Int)

创建选择标记为expireAt / version

的产品元素的路径
val (expirePath, versionPath) = (^.expireAt, ^.version)

编写接受镜头工厂的方法以获取给定路径:

def refresh[T](t: T)(implicit
                    expireL: expirePath.Lens[T, Instant],
                    versionL: versionPath.Lens[T, Int]): T = {
  val t1 = expireL().set(t)(Instant.now())
  val t2 = versionL().modify(t1)(_ + 1)

  t2
}

使用它:

println(refresh(A(1, null, 0)))
println(refresh(B("The B", null, 4)))