說我有這個層次結構
trait Base {
val tag: String
}
case class Derived1(tag: String = "Derived 1") extends Base
case class Derived2(tag: String = "Derived 2") extends Base
//etc ...
我想用以下簽名定義方法
def tag[T <: Base](instance: T, tag: String): T
T回傳帶有 modified的型別的實體tag: String。因此,例如,當一個Derived1實體被傳入相同型別的修改實體時,將回傳。
這個目標可以通過使用可變tag變數輕松實作var tag: String。如何使用 scala 和函式式編程實作所需的行為?
我的想法:
我可以創建一個型別類及其實體
trait Tagger[T] {
def tag(t: T, state: String): T
}
implicit object TaggerDerived1 extends Tagger[Derived1] {
override def tag(t: Derived1, state: String): Derived1 = ???
}
implicit object TaggerDerived2 extends Tagger[Derived2] {
override def tag(t: Derived2, state: String): Derived2 = ???
}
implicit object TaggerBase extends Tagger[Base] {
override def tag(t: Base, state: String): Base = ???
}
和一個方法
def tag[T <: Base](instance: T, tag: String)(implicit tagger: Tagger[T]): T = tagger.tag(instance, tag)
這并不理想,因為首先用戶在定義自己的派生類時必須意識到這一點。當沒有定義一個時,隱式決議將回退到基本實作并縮小回傳型別。
case class Derived3(tag: String = "Derived 3") extends Base
tag(Derived3(), "test") // falls back to `tag[Base](...)`
現在我傾向于通過使用可變狀態來使用var tag: String. 但是,我很想聽聽一些意見,如何在 scala 中純粹從功能上解決這個問題。
uj5u.com熱心網友回復:
您可以派生您的型別類Tagger,然后用戶不必為每個擴展的新案例類定義其實體Base
// libraryDependencies = "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.labelled.{FieldType, field}
import shapeless.{::, HList, HNil, LabelledGeneric, Witness}
trait Tagger[T] {
def tag(t: T, state: String): T
}
trait LowPriorityTagger {
implicit def notTagFieldTagger[K <: Symbol : Witness.Aux, V, T <: HList](implicit
tagger: Tagger[T]
): Tagger[FieldType[K, V] :: T] =
(t, state) => t.head :: tagger.tag(t.tail, state)
}
object Tagger extends LowPriorityTagger {
implicit def genericTagger[T <: Base with Product, L <: HList](implicit
generic: LabelledGeneric.Aux[T, L],
tagger: Tagger[L]
): Tagger[T] = (t, state) => generic.from(tagger.tag(generic.to(t), state))
implicit val hnilTagger: Tagger[HNil] = (_, _) => HNil
implicit def tagFieldTagger[T <: HList]:
Tagger[FieldType[Witness.`'tag`.T, String] :: T] =
(t, state) => field[Witness.`'tag`.T](state) :: t.tail
}
case class Derived1(tag: String = "Derived 1") extends Base
case class Derived2(tag: String = "Derived 2") extends Base
case class Derived3(i: Int, tag: String = "Derived 3", s: String) extends Base
tag(Derived1("aaa"), "bbb") // Derived1(bbb)
tag(Derived2("ccc"), "ddd") // Derived2(ddd)
tag(Derived3(1, "ccc", "xxx"), "ddd") // Derived3(1,ddd,xxx)
或者,對于單引數案例類,您可以進行約束T,使其具有.copy
import scala.language.reflectiveCalls
def tag[T <: Base {def copy(tag: String): T}](instance: T, tag: String): T =
instance.copy(tag = tag)
對于多引數案例類,.copy由于方法簽名變得未知(要計算),因此很難用型別表示存在。
所以你可以做tag一個宏
// libraryDependencies = scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def tag[T <: Base](instance: T, tag: String): T = macro tagImpl
def tagImpl(c: blackbox.Context)(instance: c.Tree, tag: c.Tree): c.Tree = {
import c.universe._
q"$instance.copy(tag = $tag)"
}
或者您可以使用運行時反射(Java 或 Scala,是否使用Product功能)
import scala.reflect.{ClassTag, classTag}
import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe.{TermName, termNames}
def tag[T <: Base with Product : ClassTag](instance: T, tag: String): T = {
// Product
val tagIdx = instance.productElementNames.zipWithIndex
.find{ case (fieldName, idx) => fieldName == "tag" }.map(_._2).get
val values = instance.productIterator.zipWithIndex
.map {case (fieldValue, idx) => if (idx == tagIdx) tag else fieldValue}.toSeq
// Java reflection
// val clazz = instance.getClass
// clazz.getMethods.find(_.getName == "copy").get.invoke(instance, values: _*).asInstanceOf[T]
// clazz.getConstructors.head.newInstance(values: _*).asInstanceOf[T]
// Scala reflection
val clazz = classTag[T].runtimeClass
val classSymbol = rm.classSymbol(clazz)
// val copyMethodSymbol = classSymbol.typeSignature.decl(TermName("copy")).asMethod
// rm.reflect(instance).reflectMethod(copyMethodSymbol)(values: _*).asInstanceOf[T]
val constructorSymbol = classSymbol.typeSignature.decl(termNames.CONSTRUCTOR).asMethod
rm.reflectClass(classSymbol).reflectConstructor(constructorSymbol)(values: _*).asInstanceOf[T]
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/512154.html
