【问题标题】:Variable number of types on a trait特征上可变数量的类型
【发布时间】:2011-12-24 18:45:03
【问题描述】:

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

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)

它可以正常工作,但是当我需要缓存具有更多参数的函数时就会出现问题 - 所以我需要开发 trait Cache2、Cache3,所有从第一个 trait 复制粘贴代码。

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

有没有办法更普遍地做到这一点并避免DRY principle违规?

【问题讨论】:

标签: scala


【解决方案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)应用到函数时,它会对函数进行元组处理,对元组函数进行装饰,并对被装饰的函数进行解元组,以便可以以与原始函数相同的方式调用它。

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      可能的解决方案是对计算函数使用可变参数:

      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)
        }
      }
      

      但我确实相信有更清洁和更聪明的工作。

      【讨论】:

      • 但这仅适用于相同类型的列表。而且您必须实现要缓存的函数,以便它们接受列表。
      • @Silas 我已经重新设计了实现,现在这是真正的可变参数(但是是的,参数仍然必须共享相同的类型)。
      【解决方案4】:

      您至少可以自动对函数进行元组处理,以便您可以定义接受常用参数列表的真实函数(例如 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,不重复整个Cache代码。

      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)

      【讨论】:

        猜你喜欢
        • 2014-09-01
        • 1970-01-01
        • 2011-09-08
        • 2012-04-23
        • 1970-01-01
        • 2020-10-27
        • 1970-01-01
        • 2019-02-19
        • 1970-01-01
        相关资源
        最近更新 更多