可在任何情况下运行的 scala 函数 class

scala function to operate on any case class

我一直在尝试创建一个函数,在任何情况下都会做一些变形 class 但无法弄清楚如何在非具体泛型类型上实现它,我做错了什么?

请注意,我无法更改传递给此函数的原始大小写 classes ** 编辑 ** 忘了说我使用的是 pre 2.13 scala 并且更喜欢不依赖外部库的解决方案。

        def caseClassToArray[A](something: A) = {
          classOf[A]
          .getDeclaredFields
          .map { f: Field =>
          f.setAccessible(true)
          val res = (f.getName, f.get(something))
          f.setAccessible(false)
          res
        }
    }

Scala 与 Java 一样,会在运行时擦除泛型。因此,如果您想在运行时使用泛型类型 A 执行某些操作,则需要使用 implicit ClassTag argument.

import scala.reflect._
import java.lang.reflect._

def caseClassToArray[A](something: A)(implicit cls: ClassTag[A]) = {
  cls.runtimeClass // <-- Instead of classOf[A]
    .getDeclaredFields
    .map { f: Field =>
      f.setAccessible(true)
      val res = (f.getName, f.get(something))
      f.setAccessible(false)
      res
    }
}

在 Scala 3 中,看起来像

def caseClassToArray[A](something: A)(using cls: ClassTag[A]) = {
  ...
}

如果您正在寻找一种进行反射的方法,@Silvio 的答案很好。

但是要实现相同的输出,您应该依赖库提供的标准方法,而不是使用反射:

def caseClassToArray[A <: Product](instance: A): Array[(String, Any)] = {
  a.productElementNames.zipWith(a.productIterator)
}

参见 https://www.scala-lang.org/api/current/scala/Product.html(所有情况 类 继承自 Product)。

鉴于评论中讨论的内容,最好的解决方案似乎是 Diff typeclass 并使用 Shapeless 用于案例 classes.

的推导

第一步是定义类型class。

trait Diff[A] {
  def diff(a1: A, a2: A): A
}

You may need to adjust the definition for your exact use case.

下一步是为 IntStringList[A] 等具有 Diff 的任何 A 等基本标准类型提供支持,等等

object Diff {
  implicit final val IntDiff: Diff[Int] =
    new Diff[Int] {
      override def diff(i1: Int, i2: Int): Int =
        i1 - i2
    }

  ...
}

You may also want to add extension methods, implicitNotFound error message, and everything just like any other typeclass.
That stuff is out of the scope of this answer.

下一步是使用 Shapeless 归纳派生任何 HList 的实例,其所有元素类型都具有 Diff 关联。

import shapeless.{:: => :!:, HList, HNil}

sealed trait ReprDiff[R <: HList] extends Diff[R]
object ReprDiff {
  implicit final val HNilReprDiff: ReprDiff[HNil] =
    new ReprDiff[HNil] {
      override final def diff(nil1: HNil, nil2: HNil): HNil = HNil
    }
  
  implicit def HConsReprDiff[H, T <: HList](
    implicit ev: Diff[H], tailReprDiff: ReprDiff[T]
  ): ReprDiff[H :!: T] = new ReprDiff[H :!: T] {
    override final def diff(hlist1: H :!: T, hlist2: H :!: T): H :!: T = {
      val headDiff = ev.diff(hlist1.head, hlist2.head)
      val tailDiff = tailReprDiff.diff(hlist1.tail, hlist2.tail)
      
      headDiff :: tailDiff
    }
  }
}

最后,使用 Shapeless Generic 魔法,我们可以为存在其 HList 实例的任何情况 class 提供实例代表。

import shapeless.Generic

sealed trait DerivedDiff[P <: Product] extends Diff[P]
object DerivedDiff {
  implicit def instance[P <: Product, R <: HList](
    implicit gen: Generic.Aux[P, R], ev: ReprDiff[R]
  ): DerivedDiff[P] = new DerivedDiff[P] {
    override final def diff(p1: P, p2: P): P =
      gen.from(ev.diff(gen.to(p1), gen.to(p2)))
  }
}

gen.to turns any instance of a case class P into an HList that represents it.
gen.from turns a matching instance of an HList into a P

现在,我们所需要的只是一种让我们的用户为他们的案例 class 派生实例的方法,只要所有字段都有一个 Diff 与之关联。
我个人更喜欢 semiauto 方法:

// Somewhere, like in the companion object of Diff
def derive[P <: Product](implicit ev: Deriving.DerivedDiff[P]): ev.type = ev

综上所述,您可以像这样使用它:

final case class Foo(a: Int, b: Int)
object Foo {
  implicit final val FooDiff: Diff[Foo] =
    Diff.derive[Foo]
}

恭喜,您现在可以 diff 两个 Foo 个实例:

val foo1 = Foo(1, 3)
val foo2 = Foo(0, 5)

val diff = Diff.diff(foo1, foo2)
// res: Foo = Foo(1, -2)

In this example we added an auxiliary method diff in the Diff companion object; you may provide any other API to call the diff operation.


可以看到代码运行 here.