读书人

CLisp 23:运行时批改函数的实现

发布时间: 2012-12-31 11:57:52 作者: rapoo

CLisp 23:运行时修改函数的实现

运行时修改函数的实现,对LISP来说并不是稀奇事,用defun重新定义一个即可。这里介绍一个具体的、高级的做法,可以加深理解LISP函数的本质。例子很简单,只有几行代码,先定义一个函数foo,作用是乘方,然后再改掉foo的实现,新的实现仍然可以使用函数foo的原有实现。

(defun foo (x) (* x x))

(let* ((oldf (symbol-function 'foo))

(newf #'(lambda (x)

(* x (funcall oldf x)))))

(setf (symbol-function 'foo) newf))

上面例子修改了函数foo的实现,也可以不修改foo的实现,让它完成本该完成的事情,同时做一些额外的事情,例如统计调用次数,或者统计执行时间,或者记录每次调用的输入参数,等等。

LISP的Trace工具用的就这个原理,例如

[5]> (defun my-nth (n x) 先定义一个函数,取列表x中第n个元素

(cond ((zerop n) (first x))

(t (my-nth (- n 1) (rest x)))))

MY-NTH

[7]> (dtrace my-nth) 声明要跟踪函数my-nth的调用情况,用的是dtrace版本

(MY-NTH)

[8]> (my-nth 2 '(a b c d e)) 再执行my-nth时,会打印详细的调用过程

----Enter MY-NTH

| Arg-1 = 2

| Arg-2 = (A B C D E)

| ----Enter MY-NTH

| | Arg-1 = 1

| | Arg-2 = (B C D E)

| | ----Enter MY-NTH

| | | Arg-1 = 0

| | | Arg-2 = (C D E)

| | \--MY-NTH returned C

| \--MY-NTH returned C

\--MY-NTH returned C

C 这是最后的返回结果

DTrace的源代码很短,下面是主体部分,输入函数名,把该函数的实现替换掉

(defun trace-function (name)

(let* ((formal-arglist (fetch-arglist name)) 获取原函数的参数列表,形参

(old-defn (symbol-function name)) 取到原来的函数

(new-defn 定义新的函数

#'(lambda (&rest argument-list)

(let ((result nil))

(display-function-entry name) 显示函数名称

(let ((*trace-level* (1+ *trace-level*)))

(with-dtrace-printer-settings

(show-function-args argument-list formal-arglist)) 显示输入参数,实参

(setf result (multiple-value-list

(apply old-defn argument-list)))) 调用原函数

(display-function-return name result) 显示执行结果

(values-list result))))) 返回执行结果

(setf (get name 'original-definition) old-defn) 保存原函数实现,恢复时使用

(setf (get name 'traced-definition) new-defn)

(setf (get name 'traced-type) 'defun)

(setf (symbol-function name) new-defn))) 用新函数替换原函数

注:在Common LISP中,可以用system::arglist获取函数的形参列表

1楼cmdblock6天前 16:52
高手,膜拜下

读书人网 >编程

热点推荐