Dom*_*riš 8 common-lisp memoization land-of-lisp
在329页的Lisp的土地,康拉德Barski解释的技术记忆化与下面的示例代码
(let ((old-neighbors (symbol-function 'neighbors))
(previous (make-hash-table)))
(defun neighbors (pos)
(or (gethash pos previous)
(setf (gethash pos previous) (funcall old-neighbors pos)))))
Run Code Online (Sandbox Code Playgroud)
这个想法很好:当我调用该neighbors函数时,我将结果保存到哈希表中,以便下次neighbors使用相同的值调用时pos,我可以查找结果而无需再次计算它.
所以我想知道,是否不会是更容易的功能重新命名neighbors,以old-neighbors通过编辑和重新编译它的源代码(书的314页上给出).然后可以将memoization示例简化为
(let ((previous (make-hash-table)))
(defun neighbors (pos)
(or (gethash pos previous)
(setf (gethash pos previous) (funcall old-neighbors pos)))))
Run Code Online (Sandbox Code Playgroud)
或者,通过事先previous变成一个全局变量*previous-neighbors*,甚至进入
(defun neighbors (pos)
(or (gethash pos *previous-neighbors*)
(setf (gethash pos *previous-neighbors*) (funcall old-neighbors pos))))
Run Code Online (Sandbox Code Playgroud)
从而使封闭不必要.
所以我的问题是:这样做的原因是什么?
我能想象的原因:
symbol-function.neighbors.你已经做了一些很好的观察.
通常,使用类似闭包的样式更可能在Scheme代码中找到 - 其中Scheme开发人员经常使用函数来返回函数.
通常,正如您已经检测到的那样,使用memoize函数foo调用函数是没有意义的old-foo.使用全局变量减少了封装( - > 信息隐藏),但增加了调试程序的能力,因为可以更容易地检查已记忆的值.
一个类似但可能更有用的模式是:
(defun foo (bar)
<does some expensive computation>)
(memoize 'foo)
Run Code Online (Sandbox Code Playgroud)
'memoize'会是这样的
(defun memoize (symbol)
(let ((original-function (symbol-function symbol))
(values (make-hash-table)))
(setf (symbol-function symbol)
(lambda (arg &rest args)
(or (gethash arg values)
(setf (gethash arg values)
(apply original-function arg args)))))))
Run Code Online (Sandbox Code Playgroud)
优点是您不需要为每个函数编写memoizing代码.你只需要一个功能memoize.在这种情况下,闭包也是有意义的 - 尽管你也可以有一个存储memoize表的全局表.
注意上面的限制:比较使用EQL并且只使用函数的第一个参数进行memoize.
还有更广泛的工具来提供类似的功能.
参见例如:
https://github.com/sharplispers/cormanlisp/blob/master/examples/memoize.lisp
使用memoize上面的我:
CL-USER 22 > (defun foo (n)
(sleep 3)
(expt 2 n))
FOO
CL-USER 23 > (memoize 'foo)
#<Closure 1 subfunction of MEMOIZE 40600008EC>
Run Code Online (Sandbox Code Playgroud)
使用arg的第一次调用10运行三秒:
CL-USER 24 > (foo 10)
1024
Run Code Online (Sandbox Code Playgroud)
使用arg的第二次调用10运行得更快:
CL-USER 25 > (foo 10)
1024
Run Code Online (Sandbox Code Playgroud)
使用arg的第一次调用2运行三秒:
CL-USER 26 > (foo 2)
4
Run Code Online (Sandbox Code Playgroud)
使用arg的第二次调用2运行得更快:
CL-USER 27 > (foo 2)
4
Run Code Online (Sandbox Code Playgroud)
与arg的第三次调用10运行速度很快:
CL-USER 28 > (foo 10)
1024
Run Code Online (Sandbox Code Playgroud)