通常重写Scala案例类

时间:2012-11-15 16:53:21

标签: scala reflection case-class shapeless

是否可以一般性地替换案例类中的参数?更具体地说,我想要一个替代函数,它接收一个“查找”案例类和一个“替换”案例类(如语法规则的左侧和右侧)以及一个目标案例类,该函数将返回一个将case case类的参数替换为replace case类的新case类?该函数还可以简单地采用案例类(Product?)和一个应用于案例类的所有参数/产品的函数。

显然,给定一个特定的案例类,我可以使用unapply和apply - 但是一般来说(给定任何案例类)编写这种函数的最佳/最简单/等方法是什么?

我想知道是否有一个很好的解决方案,使用Scala 2.10反射功能或无形的Iso.hlist。

例如,我真正希望能够做的是,给出类似以下的类......

class Op[T]
case class From(x:Op[Int]) extends Op[Int]
case class To(x:Op[Int]) extends Op[Int]

case class Target(a:Op[Int], b:Op[Int]) extends ...
// and lots of other similar case classes

...有一个函数可以接受一个任意的case类,并返回一个类型为From的元素替换为To类型的实例。

3 个答案:

答案 0 :(得分:5)

如果你原谅插件,我想你会发现我们Kiama language processing library的重写组件非常适合这种用途。它提供了一种非常强大的战略规划形式。

这是一个完整的解决方案,可以在由案例类实例构成的树中将To重写为From

import org.kiama.rewriting.Rewriter

class Op[T]
case class Leaf (i : Int) extends Op[Int]
case class From (x : Op[Int]) extends Op[Int]
case class To (x : Op[Int]) extends Op[Int]

case class Target1 (a : Op[Int], b : Op[Int]) extends Op[Int]
case class Target2 (c : Op[Int]) extends Op[Int]

object Main extends Rewriter {

    def main (args : Array[String]) {
        val replaceFromsWithTos =
            everywhere {
                rule {
                    case From (x) => To (x)
                }
            }

        val t1 = Target1 (From (Leaf (1)), To (Leaf (2)))
        val t2 = Target2 (Target1 (From (Leaf (3)), Target2 (From (Leaf (4)))))

        println (rewrite (replaceFromsWithTos) (t1))
        println (rewrite (replaceFromsWithTos) (t2))
    }

}

输出

Target1(To(Leaf(1)),To(Leaf(2)))
Target2(Target1(To(Leaf(3)),Target2(To(Leaf(4)))))

replaceFromsWithTos值的概念是rule构造提升了部分函数,​​以便能够对任何类型的值进行操作。在这种情况下,部分函数仅在From个节点定义,用To个节点替换它们。 everywhere组合子说“将我的参数应用于树中的所有节点,保留参数不适用的不变位置。

比这种简单的重写还要多得多。有关详细信息,请参阅the main Kiama rewriting documentation,包括更多示例的链接。

答案 1 :(得分:4)

我认为你真的找不到比通过模式匹配使用unapply / apply更好的方法:

someValue match {
  case FindCaseClass(a, b, c) => ReplaceCaseClass(a, b, c)
  // . . .
}

你必须写出规则,以某种方式将FindCaseClassReplaceCaseClass联系起来,虽然你可以通过某种方式使用这些名称更简洁地做到这一点,但这有额外的好处还要在编译时检查案例类字段的数量和类型,以确保所有内容都匹配正确。

使用所有案例类扩展Product的事实可能有一些方法可以自动执行此操作,但productElement(n)返回Any这一事实可能会让它有点痛苦 - 我认为这就是必须反思的地方。这里有一些东西让你开始:

case class From(i: Int, s: String, xs: Seq[Nothing])
case class To(i: Int, s: String, xs: Seq[Nothing])

val iter = From(5,"x",Nil).productIterator
val f = To.curried
iter.foldLeft(f: Any) { _.asInstanceOf[Any => Any](_) }
// res0: Any = To(5,x,List())

但实际上,我认为你的模式匹配版本会更好。

编辑: 以下是将重构代码重构为方法的版本:

case class From(i: Int, s: String, xs: Seq[Nothing])
case class To(i: Int, s: String, xs: Seq[Nothing])

type Curryable = { def curried: _ => _ }

def recase(from: Product, to: Curryable) = {
  val iter = from.productIterator
  val f = to.curried
  iter.foldLeft(f: Any) { _.asInstanceOf[Any => Any](_) }
}

recase(From(5,"x",Nil), To)
// res0: Any = To(5,x,List())

答案 2 :(得分:4)

我用无形的方式进行了一些实验,并且能够提出以下相对通用的方法将一个案例类转换为另一个案例类:

import shapeless._ /* shapeless 1.2.3-SNAPSHOT */

case class From(s: String, i: Int)
case class To(s: String, i: Int)

implicit def fromIso = Iso.hlist(From.apply _, From.unapply _)
implicit def toIso = Iso.hlist(To.apply _, To.unapply _)

implicit def convert[A, B, L <: HList]
                   (a: A)
                   (implicit srcIso: Iso[A, L],
                             dstIso: Iso[B, L])
                   : B =
  dstIso.from(srcIso.to(a))

val f1 = From("Hi", 7)
val t1 = convert(f1)(fromIso, toIso)

println("f1 = " + f1) // From("Hi", 7)
println("t1 = " + t1) // To("Hi", 7)

然而,我无法正确理解。理想情况下,

val t1: To = f1

就足够了,或者

val t1 = convert(f1)

另一个不错的改进是摆脱了为每个案例类明确声明iso-implicits(fromIsotoIso)的需要。