【问题标题】:Scala/Shapeless: Updating named field in case class instanceScala/Shapeless:在案例类实例中更新命名字段
【发布时间】:2017-11-24 09:18:54
【问题描述】:

我正在尝试创建一个类型类,它允许我在任何案例类上增加一个名为“counter”的 Int 字段,只要该类具有这样的字段。

我曾尝试用 Shapeless 做到这一点,但我碰壁了(在第一次尝试消化“The Type Astronaut's Guide to Shapeless”、Shapeless 2.0.0 的“功能概述”和 Stack Overflow 上的众多线程之后)。

我想要的是能够做类似的事情

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

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

它应该适用于任何具有合适计数器字段的案例类。

我认为使用类型类和 Shapeless 的记录抽象(以及将增量功能作为方法添加的隐式转换)是可能的。裸露的骨头会是这样的:

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

但是,这不会编译。错误是value replace is not a member of generator.Repr。我猜这是因为编译器不能保证 T 有一个名为counter 的字段,并且它的类型为Int。但我怎么能这么说呢?关于 Shapeless 的记录是否有更好/更多的文档?或者这是一个完全错误的方法?

【问题讨论】:

    标签: scala generics typeclass shapeless


    【解决方案1】:

    您只需要隐含地要求 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))
    }
    

    【讨论】:

    • 我很好奇,这是什么Witness.`'counter`.T
    • @CyrilleCorpet 这是'counter Symbol 的唯一实例类型。这就是无形的Records 用于他们的钥匙。 Witness 也可用于为 Symbol 以外的类型的实例创建唯一类型,例如 Witness.`"some string"`.T
    • @Kolmar 太好了!我以为是这样,但我不知道动态成员查找以及 Witness 是如何使用它的。
    • 谢谢。我很确定使用 Shapeless 可以做到这一点,但不知道修饰符类型。
    • 我在编译一个稍微修改过的版本时遇到了很多问题。事实证明,如果我用任何其他元类型替换 T,就会出现错误:implicit class Incrementer[X, L &lt;: HList](t: X)( implicit gen: LabelledGeneric.Aux[X, L], modifier: Modifier.Aux[L, Witness.`'counter`.X, Int, Int, L] ) { def increment(): X = gen.from(modifier(gen.to(t), _ + 1)) } Error is type X is not a member of AnyRef{type T = ...} 你知道这是为什么吗?
    【解决方案2】:

    您可以通过简单的类型类派生轻松做到这一点:

    trait Incrementer[T] {
      def inc(s: Symbol)(t: T): T
    }
    
    object Incrementer {
      def apply[T](implicit T: Incrementer[T]): Incrementer[T] = T
      implicit def head[Key <: Symbol, Head, Tail <: HList](implicit Key: Witness.Aux[Key], Head: Numeric[Head]) = new Incrementer[FieldType[Key, Head] :: Tail] {
        import Head._
        override def inc(s: Symbol)(t: FieldType[Key, Head] :: Tail): (FieldType[Key, Head] :: Tail) =
          if (s == Key.value) (t.head + fromInt(1)).asInstanceOf[FieldType[Key, Head]] :: t.tail
          else t
      }
    
      implicit def notHead[H, Tail <: HList](implicit Tail: Incrementer[Tail]) = new Incrementer[H :: Tail] {
        override def inc(s: Symbol)(t: H :: Tail): H :: Tail = t.head :: Tail.inc(s)(t.tail)
      }
    
      implicit def gen[T, Repr](implicit gen: LabelledGeneric.Aux[T, Repr], Repr: Incrementer[Repr]) = new Incrementer[T] {
        override def inc(s: Symbol)(t: T): T = gen.from(Repr.inc(s)(gen.to(t)))
      }
    }
    
    case class Count(counter: Int)
    case class CountAndMore(more: String, counter: Int)
    case class FakeCount(counter: Long)
    object Test extends App {
    
      println(Incrementer[Count].inc('counter)(Count(0)))
      println(Incrementer[CountAndMore].inc('counter)(CountAndMore("", 0)))
      println(Incrementer[FakeCount].inc('counter)(FakeCount(0)))
    }
    

    【讨论】:

      猜你喜欢
      • 2011-02-17
      • 2015-10-19
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      • 2019-03-04
      • 1970-01-01
      • 2020-07-29
      • 1970-01-01
      相关资源
      最近更新 更多