Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何用派生对象替换树型Scala案例类实例中的对象?_Scala_Inheritance_Reflection_Traits_Case Class - Fatal编程技术网

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

如何用派生对象替换树型Scala案例类实例中的对象?,scala,inheritance,reflection,traits,case-class,Scala,Inheritance,Reflection,Traits,Case Class,假设我有一组case类,它们表示常量、变量以及对它们的一元和二元操作,类似于Scala编程中“case类和模式匹配”一章中的一个: abstract class Value { def basicEvaluate(varArray: Array[Double]): Double def evaluate(varArray: Array[Double]) = basicEvaluate(varArray) } case class Constant(d: Double) exte

假设我有一组case类,它们表示常量、变量以及对它们的一元和二元操作,类似于Scala编程中“case类和模式匹配”一章中的一个:

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))
它是有效的

我的问题是-如何实现函数
def extend(value:value):使用cache
,它将递归地用相应的
替换树中的每个
值。。是否使用缓存
对象?我希望将此逻辑与
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()
//}
def extend(值:value):使用缓存=宏extendImpl
def extendImpl(c:blackbox.Context)(值:c.Tree):c.Tree={
导入c.universe_
def transformExprss(exprss:Seq[Seq[Tree]]):Seq[Seq[Tree]]=
exprss.map(u.map(expr=>if(expr.tpe
val exprss1=transformExprss(exprss)
q“使用缓存新建$expr.${tname.toTypeName}(…$exprss1)”
案例q“${tname:TermName}.apply(…$exprss)”=>
val exprss1=transformExprss(exprss)
q“使用缓存新建${tname.toTypeName}(…$exprss1)”
}
}
扩展(添加(常量(1.0)、变量(2)))
//警告:scalac:正在执行宏扩展App.extend(App.Add.apply(App.Constant.apply(1.0)、App.Variable.apply(2)),位于。。。
//警告:scalac:{
//最后一个类$anon使用缓存扩展App.Add(扩展(App.Constant.apply(1.0))、扩展(App.Variable.apply(2))){
//def()={
//超级(;
//      ()
//    }
//  };
//新$anon()
//}
//警告:scalac:正在以下位置执行宏扩展App.extend(App.Constant.apply(1.0))。。。
//警告:scalac:{
//最后一个类$anon使用缓存扩展了App.Constant(1.0){
//def()={
//超级(;
//      ()
//    }
//  };
//新$anon()
//}
//警告:scalac:正在。。。
//警告:scalac:{
//最后一个类$anon使用缓存扩展App.Variable(2){
//def()={
//超级(;
//      ()
//    }
//  };
//新$anon()
//}

这里有一个解决方案,它使用堆栈进行深度优先遍历。它是尾部调用优化的,因此不会出现堆栈溢出。OP还要求重用旧的缓存值,因此使用映射进行记忆

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
      }

    }
  }

}

如果您与
Value
的所有子类型匹配,为什么不直接写入
def extend(Value:Value):UsingCache=Value匹配{case Constant(d)=>new Constant(d)with UsingCache;case变量(i)=>new Variable(i)with UsingCache;case Add(v1,v2)=>new Add(extend(v1,extend(v2))with UsingCache}
?你为什么需要堆栈?是的,没错,你不需要堆栈。你的解决方案比较好,因为它比较短。@AllenHan实际上,为每个操作
X
都有一个单独的类
CachedX
,这正是我想要避免的……它会产生太多的样板代码,而且看起来不美观、不简洁,就像在Scala are.@DmytroMitin,您的解决方案存在一个问题(除了需要提及每个特定类之外),即每个递归调用(例如,在
案例中添加(v1,v2)=>新添加(扩展(v1),扩展(v2))
)将创建不同的对象来替换
v1
v2
,即使它们最初是同一个对象。这将使缓存无效。@AndreySh我理解。我以这种方式实现它,以便可以通过println打印出来轻松检查该类型。我仍在考虑如何实现它,以便它不会被损坏所有调用都已优化。我可能无法及时完成它的实现,让您使用它,但工作起来很有趣。感谢您分享您的问题。感谢您的回答-它为我打开了Scala宏的全新世界:)然而,
extend(Add(常量(1.0),Variable(2))
工作完美,
val expr=Add(常量(1.0),变量(2));extend(expr)
不需要,因此宏可能需要一些工作。我现在考虑递归工作,在反射的帮助下获得字段名(如
Add
中的
v1
v2
),并“编译”代码,如
newadd(v1,v2)通过使用宏来使用缓存。然而,我还远没有一个完整的解决方案…@AndreySh对,
val expr=Add(常量(1.0),变量(2));extend(expr)
将无法工作。
Add(常量(1.0),变量(2))
expr
的运行时值,宏在编译时展开,因此无法访问运行时值。我在您使用缓存的实现中发现了一个错误。无论varArray的值是多少,您的缓存值都是相同的。有关更正的版本,请参阅我的解决方案。此外,正如我在回答的注释中所述,我更新的answer被记忆并使用尾部调用优化。对不起,我的错误,case类是不可变的,所以您使用Cache的实现很好。我的错误。我将我的实现更改回与您的实现相同。事实上,对不起,我毕竟是对的。想想看。对于类型变量的缓存值,您的求值函数需要返回dif如果传入不同的数组,则返回不同的值。因此,在这种情况下,您的实现将返回错误的结果。我已将我的答案更改回更正的答案。可以重写我的代码以提高效率,这样使用缓存的常量映射就不必存储映射。但为此,您必须具有唯一的类f或缓存