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

Scala:部分计算函数并缓存固定值,scala,lazy-evaluation,currying,Scala,Lazy Evaluation,Currying,是否有一种简单的方法可以以纯函数的方式缓存部分应用函数的固定值 代码示例: 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) l

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

代码示例:

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
我已将
f
def
更改为
val
——这允许f成为“存储”函数的对象,而不仅仅是每次运行整个函数的方法。在这种情况下,每次仅运行
apply
,并保留函数对象的实例变量。剩下的是一种糟糕的方式

虽然这对于调用方来说是不可变的,因为返回的结果不会随时间而改变,但它不是线程安全的。您可能希望使用某种类型的同步映射来存储缓存的值

编辑: 在我写了这篇文章之后,我在谷歌上搜索“函数备忘录”,得到了类似的解决方案。但它们更通用:

显然,在Scalaz中甚至还有一些东西:)

编辑:

问题是Scala并不急于计算函数的参数,即使函数部分应用或使用了curry。它只存储参数的值。以下是一个例子:

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
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
这表明您仍然可以手动计算一次参数,并通过将其部分应用于函数(或使用curry)来“保存”参数。我想这就是你想要的。要获得更方便的方法,您可以使用此方法:

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

但是,在最后两个示例中,memorization是函数对象的本地。您必须重用相同的函数对象才能使其工作。与此不同,在第一个示例中,memorization是定义函数的范围的全局性的。

一个简单的实现:

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> 
scala>def(x:Int):Int=>Int={
|       
|def费用计算(数值:整数)={
|println(“我花了很多时间(!)计算“+num”的平方)
|num*num
|       }
|val缓存=费用计算(x)
|     
|def add(num:Int)={
|缓存+num
|       }
|       
|加
|       
|     }
f:(x:Int)Int=>Int
scala>valg=f(1)
我花了很多时间(!)计算1的平方
g:Int=>Int=
scala>g(2)
res0:Int=3
scala>g(3)
res1:Int=4
scala>g(4)
res2:Int=5
斯卡拉>

谢谢您的回答。我知道这种非FP方式。我正在寻找一种更通用、更实用的方法。但是你提供的链接看起来很有希望,特别是scalaz中的链接。我会检查的。在谷歌搜索时,我没有想到“函数记忆”这个词。谢谢。Scalaz在窗帘后面使用相同的方法:。最终你需要将状态存储在某个地方或传递给他人。你是对的。读了这篇文章后,我明白这并不像我第一眼看到的那样。我希望找到一种类似于Scala如何在内部处理惰性值的方法。事实上,现在我看到我的案例是一个非常特殊的“函数记忆”案例。当部分应用一个函数时,我们可以访问一个定义的变量,该变量绑定到它的一个实例,现在函数的某些部分是常量。因此,我认为在不定义辅助映射和计算任何哈希函数的情况下,应该可以做到这一点。稍后我将尝试编写一个脏的OO代码来实现这一点,以澄清我的观点。谢谢。你的答案比Aleksey Izmailov的答案更简洁。但我正在寻找一种更通用的方法,对函数的内部代码进行最小的修改。也许scalaz Memo类就是我要找的东西。