如何在scala中以函数形式更改基类字段



假设我有这个层次

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

其返回具有修改的CCD_ 2的类型为CCD_。因此,当例如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中纯粹从功能上解决这个问题。

您可以派生您的类型类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,使其具有T0

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 values = instance.productElementNames.zip(instance.productIterator)
.map {case fieldName -> fieldValue => if (fieldName == "tag") 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]
}

最新更新