繁体   English   中英

如何用派生对象替换树状 Scala 案例 class 实例中的对象?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM