Scala:部分计算函数并缓存固定值



是否有一种简单的方法,以纯函数的方式缓存部分应用的函数的固定值?

代码示例:

scala> def f(x:Int,y:Int)={
    def expensiveCalculation(num:Int)={
        println("I've spent a lot of time(!) calculating square of "+num)
        num*num
    }
    lazy val x2=expensiveCalculation(x)
    lazy val y2=expensiveCalculation(y)
    lazy val r=x2+y2
    r
}
scala> def g=f(1,_:Int)
scala> g(2)
I've spent a lot of time(!) calculating square of 1
I've spent a lot of time(!) calculating square of 2
res18: Int = 5
scala> g(3)
I've spent a lot of time(!) calculating square of 1
I've spent a lot of time(!) calculating square of 3
res19: Int = 10

但是我不希望为num=1调用两次昂贵的计算。因为昂贵的计算是没有副作用的,所以保存结果没有问题(请不要考虑在我的示例代码的输出流上打印的副作用)。

我可以通过手动保存状态来实现我正在寻找的使用OOP风格(尽管如果你想为它编写一个通用的可重用代码,它不是很干净)。

我认为在每个函数都没有副作用的FP中,实现这个目标应该更容易。实际上,我想不出在纯函数式语言中使用这种默认行为有什么严格的限制(除了一些实际的问题,如缓存所需的内存量等)。

你可以利用Scala同时是OOP和FP语言的事实,并且Scala中的函数是对象。

object CachedFunction extends App {
  val f = new Function2[Int, Int, Int] {
    def expensiveCalculation(num: Int) = {
      println("I've spent a lot of time(!) calculating square of " + num)
      num * num
    }
    var precomputed: Map[Int, Int] = Map()
    def getOrUpdate(key: Int): Int =
      precomputed.get(key) match {
        case Some(v) => v
        case None =>
          val newV = expensiveCalculation(key)
          precomputed += key -> newV
          newV
      }
    def apply(x: Int, y: Int): Int =
      getOrUpdate(x) + getOrUpdate(y)
  }
  def g = f(1, _: Int)
  g(2)
  g(3)
  g(3)
  f(1, 2)
}

打印:

I've spent a lot of time(!) calculating square of 1
I've spent a lot of time(!) calculating square of 2
I've spent a lot of time(!) calculating square of 3

我已经将fdef更改为val -这允许f成为"存储"函数的对象,而不仅仅是每次运行其整个主体的方法。在这种情况下,每次只运行apply,并保留函数对象的实例变量。

虽然对于调用者来说这可以被认为是不可变的,因为返回的结果不会随着时间的推移而改变,但它不是线程安全的。您可能需要使用某种同步映射来存储缓存的值。

编辑:写完这篇文章后,我在谷歌上搜索了"函数记忆",得到了类似的解决方案。但它们更通用:

Scala备忘录:这个Scala备忘录是如何工作的?

有一个通用的方法来记忆在Scala?

http://eed3si9n.com/learning-scalaz-day16

显然Scalaz里面甚至有东西:)

编辑:

问题在于,即使函数被部分应用或柯里化,Scala也不会急切地求值函数的参数。它只是存储参数的值。下面是一个例子:

object CachedArg extends App {
  def expensiveCalculation(num: Int) = {
    println("I've spent a lot of time(!) calculating square of " + num)
    num * num
  }
  val ff: Int => Int => Int = a => b => expensiveCalculation(a) + expensiveCalculation(b)
  val f1 = ff(1) // prints nothing
  val e1 = expensiveCalculation(1) // prints for 1
  val f: (Int, Int) => Int = _ + expensiveCalculation(_)
  val g1 = f(e1, _: Int)
  g1(2) // does not recalculate for 1 obviously
  g1(3)
}

打印:

I've spent a lot of time(!) calculating square of 1
I've spent a lot of time(!) calculating square of 2
I've spent a lot of time(!) calculating square of 3

这表明您仍然可以手动计算一次参数,并通过将其部分应用于函数(或柯里化)来"保存"它。我觉得这就是你想要的。为了更方便,您可以使用以下方法:

object CachedFunction extends App {
  val f = new Function1[Int, Int => Int] {
    def expensiveCalculation(num: Int) = {
      println("I've spent a lot of time(!) calculating square of " + num)
      num * num
    }
    def apply(x: Int) =
      new Function[Int, Int] {
        val xe = expensiveCalculation(x)
        def apply(y: Int) = xe + expensiveCalculation(y)
      }
  }
  val g1 = f(1) // prints here for eval of 1
  g1(2)
  g1(3)
}

打印:

I've spent a lot of time(!) calculating square of 1
I've spent a lot of time(!) calculating square of 2
I've spent a lot of time(!) calculating square of 3

然而,在最后两个例子中,记忆是局部的函数对象。您必须重用相同的函数对象才能使其工作。与此不同的是,在第一个示例中,记忆对于定义函数的作用域是全局的。

简单实现:

lazy val _f=  scala.collection.mutable.Map[Int, Int]()
def f(x: Int)(y: Int) = {
  def expensiveCalculation(num: Int) = {
    println("I've spent a lot of time(!) calculating square of " + num)
    num * num
  }
  def _cached(a:Int) =_f.getOrElseUpdate(a, expensiveCalculation(a))
  lazy val r = _cached(x)+_cached(y)
  r
}
val g=f(1)_
g(2)
g(3)

我一定是错过了什么,但为什么一个简单的闭包不适合你?

scala> def f(x:Int): Int => Int ={
     |       
     |       def expensiveCalculation(num:Int)={
     |         println("I've spent a lot of time(!) calculating square of "+num)
     |         num*num
     |       }
     |       val cached=expensiveCalculation(x)
     |     
     |       def add(num: Int) = {
     |         cached + num
     |       }
     |       
     |       add
     |       
     |     }
f: (x: Int)Int => Int
scala> val g = f(1)
I've spent a lot of time(!) calculating square of 1
g: Int => Int = <function1>
scala> g(2)
res0: Int = 3
scala> g(3)
res1: Int = 4
scala> g(4)
res2: Int = 5
scala> 

最新更新