[英]How to substitute objects in tree-like Scala case class instance with derived objects?
假设我有一组表示常量、变量以及对它们的一元和二元运算的案例类,类似于 Scala 编程中的“案例类和模式匹配”一章中的一个:
abstract class Value {
def basicEvaluate(varArray: Array[Double]): Double
def evaluate(varArray: Array[Double]) = basicEvaluate(varArray)
}
case class Constant(d: Double) extends Value {
override def basicEvaluate(varArray: Array[Double]) = d
}
case class Variable(i: Int) extends Value {
override def basicEvaluate(varArray: Array[Double]) = varArray(i)
}
case class Add(v1: Value, v2: Value) extends Value {
override def basicEvaluate(varArray: Array[Double]) = v1.evaluate(varArray) + v2.evaluate(varArray)
}
...
然后,假设我有一些方法可以生成多次重用某些子表达式的表达式树,并且我希望能够有效地评估表达式,以便每个不同的子表达式只被评估一次。 出于这个原因,我介绍一个特征
trait UsingCache extends Value {
var cached: Option[Double] = None
override def evaluate(varArray: Array[Double]) = {
if (cached == None) {
cached = Some(basicEvaluate(varArray))
}
cached.get
}
}
然后,我可以执行以下操作:
val expr = new Variable(0) with UsingCache
val expr2 = new Add(expr, expr) with UsingCache
expr2.evaluate(Array(5.0))
它有效。
我的问题是 - 如何实现 function def extend(value: Value): UsingCache
它将递归地用对应的.. with UsingCache
替换树中的每个Value
与 UsingCache object? 我希望保持这个逻辑与Value
的各个子类分离(例如,当我添加一个新操作时,它不应该包含任何特定于缓存的代码)。 有没有办法使用隐式转换来做到这一点? 或者一些想法如何使用 Scala 反射(我使用的是 Scala 2.12)?
试试宏
def extend(value: Value): UsingCache = macro extendImpl
def extendImpl(c: blackbox.Context)(value: c.Tree): c.Tree = {
import c.universe._
def transformExprss(exprss: Seq[Seq[Tree]]): Seq[Seq[Tree]] =
exprss.map(_.map(expr => if (expr.tpe <:< typeOf[Value]) q"extend($expr)" else expr))
value match {
case q"$expr.$tname.apply(...$exprss)" =>
val exprss1 = transformExprss(exprss)
q"new $expr.${tname.toTypeName}(...$exprss1) with UsingCache"
case q"${tname: TermName}.apply(...$exprss)" =>
val exprss1 = transformExprss(exprss)
q"new ${tname.toTypeName}(...$exprss1) with UsingCache"
}
}
extend(Add(Constant(1.0), Variable(2)))
//Warning:scalac: performing macro expansion App.extend(App.Add.apply(App.Constant.apply(1.0), App.Variable.apply(2))) at ...
//Warning:scalac: {
// final class $anon extends App.Add(extend(App.Constant.apply(1.0)), extend(App.Variable.apply(2))) with UsingCache {
// def <init>() = {
// super.<init>();
// ()
// }
// };
// new $anon()
//}
//Warning:scalac: performing macro expansion App.extend(App.Constant.apply(1.0)) at ...
//Warning:scalac: {
// final class $anon extends App.Constant(1.0) with UsingCache {
// def <init>() = {
// super.<init>();
// ()
// }
// };
// new $anon()
//}
//Warning:scalac: performing macro expansion App.extend(App.Variable.apply(2)) at ...
//Warning:scalac: {
// final class $anon extends App.Variable(2) with UsingCache {
// def <init>() = {
// super.<init>();
// ()
// }
// };
// new $anon()
//}
这是一个使用堆栈进行深度优先遍历的解决方案。 它是尾调用优化的,因此不会受到堆栈溢出的影响。 OP 还要求重用旧的缓存值,因此使用 map 进行记忆。
object CachedValueTest2 {
def main(args: Array[String]) = {
val expr1 = Add(Add(Constant(1), Add(Variable(1), Constant(1))), Add(Constant(2), Constant(2)))
println(extend(expr1))
val expr2 = Add(Add(Constant(1), Add(Add(Variable(2), Constant(1)), Constant(1))), Add(Constant(2), Add(Variable(1), Constant(2))))
println(extend(expr2))
}
def extend(value: Value): UsingCache = {
def replace(input: Value, stack: List[(Add, Option[UsingCache], Option[UsingCache])], map: Map[Value, UsingCache]): UsingCache = {
input match {
case in @ Constant(d) =>
val (v, newMap) = map.get(in) match {
case Some(entry) => (entry, map)
case None =>
val entry = new Constant(d) with UsingCache
(entry, map + (in -> entry))
}
popStack(v, stack, newMap)
case in @ Variable(i) =>
val (v, newMap) = map.get(in) match {
case Some(entry) => (entry, map)
case None =>
val entry = new Variable(i) with UsingCache
(entry, map + (in -> entry))
}
popStack(v, stack, newMap)
case in @ Add(v1, v2) =>
map.get(in) match {
case Some(entry) => entry
case None => replace(v1, (in, None, None) :: stack, map)
}
}
}
def popStack(input: UsingCache, stack: List[(Add, Option[UsingCache], Option[UsingCache])], map: Map[Value, UsingCache]): UsingCache = {
stack match {
case head :: tail =>
head match {
case (add, None, None) =>
replace(add.v2, (add, Some(input), None) :: tail, map)
case (add, Some(v1), None) =>
val v = new Add(v1, input) with UsingCache
val newMap = map + (add -> v)
popStack(v, tail, newMap)
}
case Nil => input
}
}
replace(value, List(), Map())
}
abstract class Value {
def basicEvaluate(varArray: Array[Double]): Double
def evaluate(varArray: Array[Double]) = basicEvaluate(varArray)
}
case class Constant(d: Double) extends Value {
override def basicEvaluate(varArray: Array[Double]) = d
}
case class Variable(i: Int) extends Value {
override def basicEvaluate(varArray: Array[Double]) = varArray(i)
}
case class Add(v1: Value, v2: Value) extends Value {
override def basicEvaluate(varArray: Array[Double]) = v1.evaluate(varArray) + v2.evaluate(varArray)
}
trait UsingCache extends Value {
var caches : Map[Array[Double], Double] = Map()
override def evaluate(varArray: Array[Double]) = {
caches.get(varArray) match {
case Some(result) =>
result
case None =>
val result = basicEvaluate(varArray)
caches = caches + (varArray -> result)
result
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.