有一种简单的方法可以以纯函数的方式缓存部分应用函数的固定值。
代码示例:
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
,并保留函数对象的实例变量。剩下的就是OOPish了。
虽然这可以被认为是调用者不可变的,因为返回的结果不会随着时间的推移而改变,但它不是线程安全的。您可能希望使用某种同步映射来存储缓存值。
编辑:在我写完这篇文章后,我搜索了“函数记忆”并得到了这些类似的解决方案。它们更通用:
Scala Memoization: How does this Scala memo work?
Is there a generic way to memoize in Scala?
http://eed3si9n.com/learning-scalaz-day16
显然,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
这表明您仍然可以手动评估一个参数,并通过将其部分应用于函数(或currying)来“保存”它。我想那就是你要追求的。要使用更方便的方法,您可以使用此方法:
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
但是,在最后两个示例中,memoizaition是函数对象的本地。您必须重用相同的函数对象才能工作。与此不同,在第一个示例中,memoizaition对于定义函数的范围是全局的。
一个简单的实现:
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>