2017-06-21 34 views
4

我想创建一个允许我在任何案例类中增加一个名为“counter”的Int字段的类型类,只要该类具有这样的类领域。斯卡拉/无形:在案例类实例中更新命名字段

我试图做到这一点与无形,但我打墙(首先试图消化“无形的类型宇航员的指南”,“无形状2.0.0”和堆栈溢出的许多线程“功能概述”)。

我想要的是能够做到像

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

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

它应该对任何情况下的班上做与适合的领域。

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

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。但我怎么能这么说呢?没有更好的/有关无形'记录的更多文档?或者这是一个完全错误的路?

回答

7

你只是含蓄地要求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)) 
} 
+0

这是相当真棒。 –

+0

我很好奇,这是什么'''Witness.''counter'.T'''? –

+1

@CyrilleCorpet这是''计数器''Symbol'唯一的实例类型。这是什么无形的“记录”用于他们的钥匙。 'Witness'可以用来为'Symbol'以外的其他类型的实例创建唯一的类型,比如''Witness.''''一些字符串'​​'.T''。 – Kolmar

0

您可以轻松地用一个简单的类型类派生做到这一点:

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