【问题标题】:scala: memoize a function no matter how many arguments the function takes?scala:无论函数有多少参数,都要记住一个函数?
【发布时间】:2011-08-18 01:27:00
【问题描述】:

我想在 scala 中编写一个 memoize 函数,它可以应用于任何函数对象,无论该函数对象是什么。我想以一种允许我使用 memoize 的单一实现的方式这样做。我对语法很灵活,但理想情况下,memoize 出现在非常接近函数声明的地方,而不是在函数之后。我还想避免先声明原始函数,然后再声明记忆版本。

所以一些理想的语法可能是这样的:

def slowFunction(<some args left intentionally vague>) = memoize {
  // the original implementation of slow function
}

甚至这是可以接受的:

def slowFUnction = memoize { <some args left intentionally vague> => {
  // the original implementation of slow function
}}

我已经看到了必须为每个 arity 函数重新定义 memoize 的方法,但我想避免这种方法。原因是我需要实现几十个类似于 memoize 的函数(即其他装饰器),并且要求为每个 arity 函数复制每个函数太多了。

一种需要你重复 memoize 声明的 memoize 方法(所以它不好)是What type to use to store an in-memory mutable data table in Scala?

【问题讨论】:

  • 澄清一下:您是否希望def foo(x: Int, y: Int) = memoize(x: Int) { factorial(x) * y } 仅记忆x 的子表达式,然后在x, y 中使用记忆的部分结果进行最终计算?
  • 实际上没有,我的期望是它会将所有函数参数一起记忆。也就是说,一个例子是“def foo(x: Int, y: Int) = memoize { factorial(x) * y) }.
  • 我想我被你的“一些参数”与“要记忆的参数”误导了,记忆参数可能是一个子集
  • 啊,谢谢 - 为了清楚起见,我删除了这些。我的意思是它们可以是 args 来控制 memoize 函数本身,而不是对应于原始函数的 args。

标签: scala


【解决方案1】:

您可以使用类型类方法来处理 arity 问题。您仍然需要处理您想要支持的每个函数 arity,但不是针对每个 arity/decorator 组合:

/**
 * A type class that can tuple and untuple function types.
 * @param [U] an untupled function type
 * @param [T] a tupled function type
 */
sealed class Tupler[U, T](val tupled: U => T, 
                          val untupled: T => U)

object Tupler {
   implicit def function0[R]: Tupler[() => R, Unit => R] =
      new Tupler((f: () => R) => (_: Unit) => f(),
                 (f: Unit => R) => () => f(()))
   implicit def function1[T, R]: Tupler[T => R, T => R] = 
      new Tupler(identity, identity)
   implicit def function2[T1, T2, R]: Tupler[(T1, T2) => R, ((T1, T2)) => R] = 
      new Tupler(_.tupled, Function.untupled[T1, T2, R]) 
   // ... more tuplers
}

然后您可以按如下方式实现装饰器:

/**
 * A memoized unary function.
 *
 * @param f A unary function to memoize
 * @param [T] the argument type
 * @param [R] the return type
 */
class Memoize1[-T, +R](f: T => R) extends (T => R) {
   // memoization implementation
}

object Memoize {
   /**
    * Memoize a function.
    *
    * @param f the function to memoize
    */
   def memoize[T, R, F](f: F)(implicit e: Tupler[F, T => R]): F = 
      e.untupled(new Memoize1(e.tupled(f)))
}

您的“理想”语法将不起作用,因为编译器会假定传递给 memoize 的块是一个 0 参数的词法闭包。但是,您可以使用后一种语法:

// edit: this was originally (and incorrectly) a def
lazy val slowFn = memoize { (n: Int) => 
   // compute the prime decomposition of n
}

编辑:

要消除定义新装饰器的大量样板,您可以创建一个特征:

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 装饰器重新定义为

object Memoize extends FunctionDecorator {
   /**
    * Memoize a function.
    *
    * @param f the function to memoize
    */
   protected def decorate[T, R](f: T => R) = new Memoize1(f)
}

您不是在 Memoize 对象上调用 memoize 方法,而是直接应用 Memoize 对象:

// edit: this was originally (and incorrectly) a def
lazy val slowFn = Memoize(primeDecomposition _)

lazy val slowFn = Memoize { (n: Int) =>
   // compute the prime decomposition of n
}

【讨论】:

  • @aaron 谢谢,这是一个非常优雅的解决方案。是否可以向 Tupler 对象添加隐式以支持具有 0 个参数的装饰函数?我正在玩这个并且无法弄清楚如何定义函数0的类型参数,因为现在只有一种类型 R 因为函数的输入没有类型。
  • @Heinrich:该语言已经支持带有 0 个参数的记忆函数:val。 :)
  • @ben 同意,我只是没有看到如何将它与上述方案结合起来,这样你只需要实现一次 decorate[T, R] 。有什么想法吗?
  • @Heinrich 更新了处理零参数函数的代码。它只需要稍微概括一下 Tupler 类型签名。
  • @acjay 我同意这需要将您的方法更改为函数值(或至少引入要委托给的函数值)有点不幸,但这是@987654331这一事实的不可避免的结果@ 每次调用时都会构造一个新的函数值/查找表。至关重要的是,我们只调用一次(我们记忆的每个函数)并将结果函数值存储在 vallazy val 中,否则记忆化的好处将失去。
【解决方案2】:

图书馆

使用 Scalaz 的scalaz.Memo

手动

以下是类似于 Aaron Novstrup 的答案和 this blog 的解决方案,除了一些更正/改进、简洁和更容易满足人们复制和粘贴的需求:)

import scala.Predef._

class Memoized[-T, +R](f: T => R) extends (T => R) {

  import scala.collection.mutable

  private[this] val vals = mutable.Map.empty[T, R]

  def apply(x: T): R = vals.getOrElse(x, {
      val y = f(x)
      vals += ((x, y))
      y
    })
}

// TODO Use macros
// See si9n.com/treehugger/
// http://stackoverflow.com/questions/11400705/code-generation-with-scala
object Tupler {
  implicit def t0t[R]: (() => R) => (Unit) => R = (f: () => R) => (_: Unit) => f()

  implicit def t1t[T, R]: ((T) => R) => (T) => R = identity

  implicit def t2t[T1, T2, R]: ((T1, T2) => R) => ((T1, T2)) => R = (_: (T1, T2) => R).tupled

  implicit def t3t[T1, T2, T3, R]: ((T1, T2, T3) => R) => ((T1, T2, T3)) => R = (_: (T1, T2, T3) => R).tupled

  implicit def t0u[R]: ((Unit) => R) => () => R = (f: Unit => R) => () => f(())

  implicit def t1u[T, R]: ((T) => R) => (T) => R = identity

  implicit def t2u[T1, T2, R]: (((T1, T2)) => R) => ((T1, T2) => R) = Function.untupled[T1, T2, R]

  implicit def t3u[T1, T2, T3, R]: (((T1, T2, T3)) => R) => ((T1, T2, T3) => R) = Function.untupled[T1, T2, T3, R]
}

object Memoize {
  final def apply[T, R, F](f: F)(implicit tupled: F => (T => R), untupled: (T => R) => F): F =
    untupled(new Memoized(tupled(f)))

  //I haven't yet made the implicit tupling magic for this yet
  def recursive[T, R](f: (T, T => R) => R) = {
    var yf: T => R = null
    yf = Memoize(f(_, yf))
    yf
  }
}

object ExampleMemoize extends App {

  val facMemoizable: (BigInt, BigInt => BigInt) => BigInt = (n: BigInt, f: BigInt => BigInt) => {
    if (n == 0) 1
    else n * f(n - 1)
  }

  val facMemoized = Memoize1.recursive(facMemoizable)

  override def main(args: Array[String]) {
    def myMethod(s: Int, i: Int, d: Double): Double = {
      println("myMethod ran")
      s + i + d
    }

    val myMethodMemoizedFunction: (Int, Int, Double) => Double = Memoize(myMethod _)

    def myMethodMemoized(s: Int, i: Int, d: Double): Double = myMethodMemoizedFunction(s, i, d)

    println("myMemoizedMethod(10, 5, 2.2) = " + myMethodMemoized(10, 5, 2.2))
    println("myMemoizedMethod(10, 5, 2.2) = " + myMethodMemoized(10, 5, 2.2))

    println("myMemoizedMethod(5, 5, 2.2) = " + myMethodMemoized(5, 5, 2.2))
    println("myMemoizedMethod(5, 5, 2.2) = " + myMethodMemoized(5, 5, 2.2))

    val myFunctionMemoized: (Int, Int, Double) => Double = Memoize((s: Int, i: Int, d: Double) => {
      println("myFunction ran")
      s * i + d + 3
    })

    println("myFunctionMemoized(10, 5, 2.2) = " + myFunctionMemoized(10, 5, 2.2))
    println("myFunctionMemoized(10, 5, 2.2) = " + myFunctionMemoized(10, 5, 2.2))

    println("myFunctionMemoized(7, 6, 3.2) = " + myFunctionMemoized(7, 6, 3.2))
    println("myFunctionMemoized(7, 6, 3.2) = " + myFunctionMemoized(7, 6, 3.2))
  }
}

当你运行 ExampleMemoize 你会得到:

myMethod ran
myMemoizedMethod(10, 5, 2.2) = 17.2
myMemoizedMethod(10, 5, 2.2) = 17.2
myMethod ran
myMemoizedMethod(5, 5, 2.2) = 12.2
myMemoizedMethod(5, 5, 2.2) = 12.2
myFunction ran
myFunctionMemoized(10, 5, 2.2) = 55.2
myFunctionMemoized(10, 5, 2.2) = 55.2
myFunction ran
myFunctionMemoized(7, 6, 3.2) = 48.2
myFunctionMemoized(7, 6, 3.2) = 48.2

【讨论】:

【解决方案3】:

我在想你可以做这样的事情,而不是使用 DynamicProxy 来实际实现。

def memo[T<:Product, R, F <: { def tupled: T => R }](f: F )(implicit m: Manifest[F]):F

这个想法是因为函数缺少一个通用的超类型,我们使用结构类型来查找可以元组的任何东西(Function2-22,你仍然需要特例 Function1)。

我把 Manifest 扔进去,这样你就可以从 F 函数 trait 构造 DynamicProxy

元组还应该有助于记忆,例如您可以简单地将元组放入 Map[T,R]

【讨论】:

    【解决方案4】:

    这是可行的,因为K 可以是元组类型,所以memo(x,y,z) { function of x, y, z } 可行:

    import scala.collection.mutable
    
    def memo[K,R](k: K)(f: => R)(implicit m: mutable.Map[K,R]) = m.getOrElseUpdate(k, f)
    

    隐式是我能看到干净地引入地图的唯一方法:

    implicit val fibMap = new mutable.HashMap[Int,Int]
    def fib(x: Int): Int = memo(x) {
        x match {
            case 1 => 1
            case 2 => 1
            case n => fib(n - 2) + fib(n - 1)
        }
    }
    

    感觉应该有可能以某种方式包装自动HashMap[K,R],这样您就不必显式地制作fibMap(并重新描述类型)。

    【讨论】:

    • 在一个理想的世界里,为了简单起见,您不必将任何参数传递给 memo。也就是说,它是一种重复的信息,已经由 fib 提供。
    • 调用 memo(x, y, z) 并将其作为单个元组传入的机制的名称是什么?
    • 我相信这实际上是解析器的一个怪癖,如下所述:stackoverflow.com/questions/2850902/…
    猜你喜欢
    • 2012-06-25
    • 1970-01-01
    • 2012-01-05
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-02
    相关资源
    最近更新 更多