编程语言可以为您处理的细节越低,让您引入错误和复杂性的地方就越少。 (经典示例是JVM上的垃圾收集和内存管理。)正如我在前几期中所强调的那样,功能语言和具有功能功能的动态语言的特征之一是,它们使您可以选择性地放弃对平凡的控制。语言和运行时的详细信息。 例如,在“ Groovy的功能特性,第1部分 ”中,我展示了递归如何消除了开发人员维护状态的需要。 在本期中,我对使用Groovy进行缓存也做同样的事情。
缓存是一个常见的要求(也是难以发现的错误的来源)。 在面向对象的语言中,缓存通常发生在数据对象级别,并且开发人员必须自己进行管理。 许多函数式编程语言通过称为备忘录的机制在函数级别构建缓存。 在本文中,我研究了函数的两个缓存用例:类内调用与外部调用。 我还说明了实现缓存的两种替代方法:手工制作的状态和备注。
方法级缓存
如果您已经阅读了本系列的任何一期文章,那么您就会熟悉数字分类的问题(在第一期中有解释)。 本文的起点是Groovy版本(在上一期中派生),如清单1所示:
清单1. Groovy中的分类器
class Classifier {
def static isFactor(number, potential) {
number % potential == 0;
}
def static factorsOf(number) {
(1..number).findAll { i -> isFactor(number, i) }
}
def static sumOfFactors(number) {
factorsOf(number).inject(0, {i, j -> i + j})
}
def static isPerfect(number) {
sumOfFactors(number) == 2 * number
}
def static isAbundant(number) {
sumOfFactors(number) > 2 * number
}
def static isDeficient(number) {
sumOfFactors(number) < 2 * number
}
}
可以通过使用我在“从功能上思考,第2部分 ”中介绍的技巧来优化清单1中 factorsOf()
方法的内部算法,但是对于此示例,我对函数级缓存更感兴趣。
由于Classifier
类对数字进行分类,因此其通用用法是通过多种方法对同一数字进行分类。 例如,考虑清单2中的代码:
清单2.数字分类的常用用法
//...
if (Classifier.isPerfect(n)) print '!'
else if (Classifier.isAbundant(n)) print '+'
else if (Classifier.isDeficient(n)) print '-'
//...
在当前的实现中,我必须为我调用的每个分类方法重新计算因子之和。 这是类内缓存的一个示例:在正常使用期间,通常每个数字多次调用sumOfFactors()
方法。 在这种常见的用例中,这是一种低效的方法。
缓存总和
使代码更高效的方法之一是利用已经完成的艰苦工作。 因为生成因子之和的成本很高,所以我只希望对每个数字执行一次。 为此,我创建了一个用于存储计算的缓存,如清单3所示:
清单3.缓存总和
class ClassifierCachedSum {
private sumCache
ClassifierCachedSum() {
sumCache = [:]
}
def su