我正在迁移过去使用 Scala 2 反射的代码。
我正在尝试使用 scala-reflection 库。
我需要获取案例类给定名称的字段值。
示例:
$ scala-cli -j system -S 3.4.2 --dep co.blocke::scala-reflection:2.0.8
scala> import co.blocke.scala_reflection.RType
scala> case class A(a: Int, b: String)
// defined case class A
scala> import co.blocke.scala_reflection.rtypes.ScalaClassRType
scala> val t = RType.of[A].asInstanceOf[ScalaClassRType[A]]
val t: co.blocke.scala_reflection.rtypes.ScalaClassRType[A] = ScalaClassRType(rs$line$2$A,rs$line$2$.A,List(),List(),List(),List(ScalaFieldInfo(0,a,IntRType(),Map(),None,None,false), ScalaFieldInfo(1,b,StringRType(),Map(),None,None,false)),Map(),List(java.lang.Object, scala.Product, java.io.Serializable),false,false,true,false,Map(),List(),List(),false)
scala> t.fields.find(_.name == "a")
val res2: Option[co.blocke.scala_reflection.rtypes.FieldInfo] = Some(ScalaFieldInfo(0,a,IntRType(),Map(),None,None,false))
scala> val c = A(3,"hola")
val c: A = A(3,hola)
在这个例子中,如何通过实例
a
的反射来获取字段c
的值。
如果更容易,我可以考虑使用其他库,例如izumi-reflect。
假设您想通过将事物一个接一个地放入 JDBC 中来序列化:
trait SerializationTarget
object SerializationTarget {
val example: SerializationTarget = new SerializationTarget {}
}
这是一个存根,我将用作您传递来调用某些 API 的东西。
trait SerializeForJDBC[A] {
def serialize(value: A, target: SerializationTarget): Unit
}
这将是一个类型类,它将采用
A
的值,并以某种方式将其所有数据放入 SerializationTarget
中 - 但看起来会像这样。
我们将从一些(存根)实例开始,展示如何处理原语:
object SerializeForJDBC {
given SerializeForJDBC[Int] with
def serialize(value: Int, target: SerializationTarget): Unit = ()
given SerializeForJDBC[Double] with
def serialize(value: Double, target: SerializationTarget): Unit = ()
given SerializeForJDBC[String] with
def serialize(value: String, target: SerializationTarget): Unit = ()
}
extension [A](value: A)
final def serializeForJDBC(target: SerializationTarget)(using
SerializeForJDBC[A]
): Unit =
summon[SerializeForJDBC[A]].serialize(value, target)
现在,当我们打电话时
10.serializeForJDBC(SerializationTarget.example)
20.0.serializeForJDBC(SerializationTarget.example)
"test".serializeForJDBC(SerializationTarget.example)
代码会选择正确的
given
并使用它,基本上就像策略模式一样,但该策略是基于类型的 DI-ed。
最后,我们将使用
Mirror
为每个 case class
生成这样的策略:
object SerializeForJDBC {
// other givens ...
import scala.compiletime.*
import scala.deriving.*
inline given derived[A <: Product](using
p: Mirror.ProductOf[A]
): SerializeForJDBC[A] = {
new SerializeForJDBC[A] {
// - p.MirroredElemTypes is a path-dependent type, which is a tuple
// containing types for the fields of a case class in order in which
// they were defined
// - Tuple.Map turns it into (SerializeForJDBC[Field1], SerializeForJDBC[Field2], ...)
// - summonAll takes the type of a tuple and returns a tuple of givens
// - that's why we had to define givens before and put them into scope
// e.g. by putting them into the companion object of a type class
private val instances =
summonAll[Tuple.Map[p.MirroredElemTypes, SerializeForJDBC]].toList
.asInstanceOf[List[SerializeForJDBC[Any]]]
def serialize(value: A, target: SerializationTarget): Unit =
// the easiest way of passing values into mirrors is to cast everything
// as Any and TypeClass[Any] and make sure that indices are correct
// e.g. using .zip
value.productIterator.zip(instances).foreach { case (field, instance) =>
instance.asInstanceOf[SerializeForJDBC[Any]].serialize(field, target)
}
}
}
}
最后我们可以称之为:
case class Example(a: Int, b: Double, c: String)
Example(1, 2.0, "3").serializeForJDBC(SerializationTarget.example)
每次需要时它都会创建一个新的
SerializeForJDBC[Example]
实例。这应该不是什么大问题,但如果我们想缓存实例,我们可以使用 derives
关键字(因为我们将基于镜像的方法定义为 derived
):
case class Example(a: Int, b: Double, c: String) derived SerializeForJDBC
Example(1, 2.0, "3").serializeForJDBC(SerializationTarget.example)
在实际代码中,您必须用与 JDBC 交互的方式替换
SerializationTarget
,替换原始 given
的主体,并调整将每个字段的实例组合成整个 case class
的实例的方式.