特征上可变数量的类型

时间:2011-11-08 20:07:32

标签: scala

我正在创建一个简单的缓存特征(轻松缓存我的函数):

trait Cache[A,B] {
  def _calc(v:A):B
  private var cache = Map[A,B]()
  def calc(v:A):B = {
    cache.get(v) match {
      case Some(x) => x
      case None => 
        val x = _calc(v)
        cache += (v -> x)
        x
    }
  }
}

用法:

object Sol extends Cache[Int,Int] {
  def _calc(v:Int):Int = { /* do something here */ }
}
Sol.calc(5)

它工作正常,但当我需要使用更多参数缓存函数时出现问题 - 所以我需要开发特征Cache2,Cache3,所有复制粘贴代码来自第一个特征。

可能的解决方法是将具有多个参数的函数转换为接受元组的函数,但这似乎不正确。

有没有办法更一般地执行此操作并避免违反DRY principle

4 个答案:

答案 0 :(得分:3)

您可以使用脚本生成具有不同arities的函数的scala源。

这种方法可能看起来很难看,但它甚至在Scala库中用于定义TupleNProductNFunctionN的源代码(其中N是一个int小于21)。

答案 1 :(得分:1)

可能的解决方案是使用varargs进行calc函数:

trait Cache[A,B] {
  def _calc(v:A*):B
  private var cache = Map[Seq[A],B]()
  def calc(v:A*):B = {
    cache.get(v.toList) match {
      case Some(x) => x
      case None => 
      val x = _calc(v:_*)
      cache += (v -> x)
      x
      }
   }
 }

object Sol1 extends Cache[Int,Int] {
  def _calc(v:Int*):Int = {
    require(v.length<2 && v.length>0, "use Sol2 for two-argument functions")
    v.head
  }
}
object Sol2 extends Cache[Int,(Int,Int)] {
  def _calc(v:Int*):(Int,Int) = {
    require(v.length<3 && v.length>1, "use Sol1 for single-argument functions")
    (v.head,v.last)
  }
}

但我相信有更干净,更聪明的工作。

答案 2 :(得分:1)

您至少可以自动编组函数,以便您可以定义接受常用参数列表的实际函数(例如add):

object Add extends Cache[(Int, Int),Int]{
    def add (a:Int,b:Int):Int = a+b
    def _calc(t:(Int, Int)) = add _ tupled t
}

通过Add.calc(3, 5)

进行调用

编辑:或者使用这种方式实现Cache2 ... CacheN而不重复整个缓存代码。

trait Cache2[A, B, C] extends Cache [(A,B), C] {
    def _calc2(a: A, b: B):C
    def _calc(t:(A,B)) = _calc2 _ tupled t
}

再次

object Add2 extends Cache2[Int,Int,Int] {
    def _calc2(a:Int, b:Int) = a+b
}

Add2.calc(3, 5)

答案 3 :(得分:1)

我不完全确定为什么你不愿意将具有多个参数的函数转换为接受元组的函数,因为无论如何你可能会将参数缓存为元组。转换可以完全在memoization实现的内部,因此它对调用者没有影响。这正是FunctionDecorator所做的:

trait FunctionDecorator {
   final def apply[T, R, F](f: F)(implicit e: Tupler[F, T => R]): F = 
      e.untupled(decorate(e.tupled(f)))

   protected def decorate[T, R](f: T => R): T => R
}

当你将一个装饰器(例如,Memoize)应用于一个函数时,它会对函数进行元组化,修饰tupled函数,并对装饰函数进行重置,以便可以像调用原始函数一样调用它。

当然,这会将您的DRY问题转移到所有函数类型的Tupler定义。我会使用paradigmatic's suggestion并使用脚本来定义Tuplers。 Tuplers比一堆CacheN类型更有用。