4

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

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

我想知道是否有使用 Scala 2.10 反射功能或来自 shapeless 的 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

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

4

3 回答 3

5

如果你原谅这个插件,我想你会发现我们的Kiama 语言处理库的重写组件非常适合这种目的。它提供了一种非常强大的战略规划形式。

这是一个完整的解决方案,它在由案例类实例构成的树中将 's重写To为's。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参数应用到树中的所有节点,保留参数不适用的地方不变。

除了这种简单的重写之外,还可以做更多的事情。有关血腥细节,请参阅主要的 Kiama 重写文档,包括指向更多示例的链接。

于 2012-11-16T21:56:24.753 回答
4

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

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

FindCaseClass您必须以某种方式写出要关联的规则ReplaceCaseClass,尽管您可以通过某种方式仅使用名称来更简洁地做到这一点,但这还具有检查案例类字段的数量和类型的额外好处编译时间以确保一切都匹配得恰到好处。

可能有某种方法可以使用所有案例类都扩展这一事实来自动执行此操作,但返回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())
于 2012-11-15T17:10:18.207 回答
4

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

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 ( fromIso, )。toIso

于 2012-11-15T21:06:22.263 回答