记忆于函数而言是指对于需要多次调用的值进行缓存的机制。目前来说,函数式的编程语言普遍都支持这种特性。
如果我们反复调用一个函数,增加一个内部缓存可以提高计算的效率,用更多的内存去换取更高的效率。
但是只有纯函数才可以适用缓存技术,纯函数是没有副作用的函数:它没有引用其他值可变的类字段。除了返回值之外不设置其他的变量,其结果完全由参数决定。
缓存
有两种函数缓存的用法,一种是外部调用,一类是内部缓存。以及两种实现方式,一种是手工管理状态,另外一种是记忆机制。
对于程序,可以用一个HashMap来存放已经计算过的值,提高下次计算的速度。
import java.util.HashMap;import java.util.Map;public class CacheValue { private static Mapresult = new HashMap<>(); public static boolean isPrime(int num) { if (result.containsKey(num)){ return true; } for(int i = 2; i <= Math.sqrt(num); i++) { if(num % i == 0) { result.put(num,false); return false; } } result.put(num,true); return true; }}
这种方式提高了运算速度,但是函数不再由纯粹的静态方法组成,类有了缓存就有了状态。带来了一系列的问题。缓存有代价:它提高了代码的非本质性和维护成本。
引入记忆
函数式语言通常会引入记忆机制来帮助开发者完成。
Java8中要记忆一个函数,需要先将需要记忆的函数定义成闭包,然后对这个闭包执行 获得一个新的函数,以后调用这个新的函数时,结果都会被缓存起来。
由于Java8,暂时在API中没有提供这种记忆机制,这个函数需要自己实现。
import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.function.Function;public class Memoizer{ private final Map cache = new ConcurrentHashMap<>(); private Memoizer() {} private Function doMemoize(final Function function) { return input -> cache.computeIfAbsent(input, function::apply); } public static Function memoize(final Function function) { //每个实例都会有独立的cache return new Memoizer ().doMemoize(function); }}
原谅我写的这个count()函数还是不够单纯。。。
public class Memoization { Functionf = this::count; Function g = Memoizer.memoize(f); public Integer count(int num){ int sum =0; for (int i = 0; i < num; i++) { sum += i; } return sum; } public void test(){ long start = System.currentTimeMillis(); Integer flag = g.apply(99999999); System.out.println(flag); System.out.println(System.currentTimeMillis()-start); start = System.currentTimeMillis(); flag = g.apply(99999999); System.out.println(flag); System.out.println(System.currentTimeMillis()-start); } public static void main(String args[]){ Memoization memoization = new Memoization(); memoization.test(); }}
这种记忆一个函数的方式是在操作函数本身,而非函数的结果。而且被记忆的内容该是值不可变的。
所以所有被记忆的函数应该是:
- 没有副作用的
- 不依赖任何外部环境。