2017-06-21 65 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))) 
}