代码之家  ›  专栏  ›  技术社区  ›  simao

使用无形状更新案例类字段

  •  1
  • simao  · 技术社区  · 7 年前

    我定义了以下类型:

    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 这会更新这些类型的两个字段,但我找不到我需要的隐式。看起来我应该可以用shapeless做这样的事情?

    def refresh[T <: RefreshableClass])(v: T)(implicit ev: LabelledGeneric[T]) = {
      val gen = ev.from(v)
    
      gen.updateWith('expire)(_.plus(...)).updateWith('version)(_+1)
    }
    
    2 回复  |  直到 7 年前
        1
  •  2
  •   Oleg Pyzhcov    7 年前

    可能最简单的方法是使用不成形透镜。首先,一些进口:

    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)))
    
        2
  •  2
  •   Dmytro Mitin    7 年前

    把你的特质封存起来。

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

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

    通常不需要指定类型参数 T R ,应推断:

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