在 Land of Lisp 的第329页,Conrad Barski用以下示例代码解释了 memoization 的技术
(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)))))
这个想法很好:当我调用neighbors
函数时,我将结果保存到哈希表中,以便下次使用相同值neighbors
调用pos
时,我可以查看结果,而无需再次计算。
所以我想知道,通过编辑和重新编译它的源代码(在本书的第314页给出)将函数neighbors
重命名为old-neighbors
是否更容易。然后,memoization示例可以简化为
(let ((previous (make-hash-table)))
(defun neighbors (pos)
(or (gethash pos previous)
(setf (gethash pos previous) (funcall old-neighbors pos)))))
或者,事先将previous
转换为全局变量*previous-neighbors*
,甚至进入
(defun neighbors (pos)
(or (gethash pos *previous-neighbors*)
(setf (gethash pos *previous-neighbors*) (funcall old-neighbors pos))))
因此不需要使用闭包。
所以我的问题是:这样做的原因是什么?
我能想象的原因:
symbol-function
的示例。neighbors
的源代码的情况下,此技术也适用。答案 0 :(得分:7)
你已经做了一些很好的观察。
通常,使用类似闭包的样式更有可能在Scheme代码中找到 - Scheme开发人员经常使用函数来返回函数。
通常,如您所检测到的那样,让memoize函数foo
调用函数old-foo
是没有意义的。使用全局变量可以减少封装( - > 信息隐藏),但可以提高调试程序的能力,因为人们可以更轻松地检查记忆的价值观。
类似但可能更有用的模式是:
(defun foo (bar)
<does some expensive computation>)
(memoize 'foo)
'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)))))))
优点是您不需要为每个函数编写记忆代码。您只需要一个函数memoize
。在这种情况下,闭包也是有意义的 - 尽管你也可以有一个存储memoize表的全局表。
请注意上述限制:比较使用EQL
并且只使用函数的第一个参数进行记忆。
还有更广泛的工具可以提供类似的功能。
参见例如:
使用上面的memoize
:
CL-USER 22 > (defun foo (n)
(sleep 3)
(expt 2 n))
FOO
CL-USER 23 > (memoize 'foo)
#<Closure 1 subfunction of MEMOIZE 40600008EC>
使用arg 10
的第一次调用运行三秒钟:
CL-USER 24 > (foo 10)
1024
使用arg 10
的第二次调用运行得更快:
CL-USER 25 > (foo 10)
1024
使用arg 2
的第一次调用运行三秒钟:
CL-USER 26 > (foo 2)
4
使用arg 2
的第二次调用运行得更快:
CL-USER 27 > (foo 2)
4
使用arg 10
的第三次调用快速运行:
CL-USER 28 > (foo 10)
1024