【问题标题】:How can I modify function bindings in Common Lisp?如何修改 Common Lisp 中的函数绑定?
【发布时间】:2015-05-03 03:29:39
【问题描述】:

您可以在 Scheme 中执行以下操作:

> (define (sum lst acc)
    (if (null? lst)
        acc
        (sum (cdr lst) (+ acc (car lst)))))
> (define sum-original sum)
> (define (sum-debug lst acc)
    (print lst)
    (print acc)
    (sum-original lst acc))
> (sum '(1 2 3) 0)
6
> (set! sum sum-debug)
> (sum '(1 2 3) 0)
(1 2 3)
0
(2 3)
1
(3)
3
()
6
6
> (set! sum sum-original)
> (sum '(1 2 3) 0)
6

如果我在 Common Lisp 中执行以下操作:

> (defun sum (lst acc)
    (if lst
        (sum (cdr lst) (+ acc (car lst)))
        acc))
SUM
> (defvar sum-original #'sum)
SUM-ORIGINAL
> (defun sum-debug (lst acc)
    (print lst)
    (print acc)
    (funcall sum-original lst acc))
SUM-DEBUG
> (sum '(1 2 3) 0)
6

现在我该如何做类似(setf sum #'sum-debug) 的事情来改变用defun 定义的函数的绑定?

【问题讨论】:

标签: variables binding scheme lisp common-lisp


【解决方案1】:

因为 Common Lisp 对函数有不同的命名空间,所以需要使用 symbol-functionfdefinition

CL-USER> (defun foo (a)
           (+ 2 a))
FOO
CL-USER> (defun debug-foo (a)
           (format t " DEBUGGING FOO: ~a" a)
           (+ 2 a))
DEBUG-FOO
CL-USER> (defun debug-foo-again (a)
           (format t " DEBUGGING ANOTHER FOO: ~a" a)
           (+ 2 a))
DEBUG-FOO-AGAIN
CL-USER> (foo 4)
6
CL-USER> (setf (symbol-function 'foo) #'debug-foo)
#<FUNCTION DEBUG-FOO>
CL-USER> (foo 4)
 DEBUGGING FOO: 4
6
CL-USER> (setf (fdefinition 'foo) #'debug-foo-again)
#<FUNCTION DEBUG-FOO-AGAIN>
CL-USER> (foo 4)
 DEBUGGING ANOTHER FOO: 4
6
CL-USER>

【讨论】:

  • (setf fdefinition) 可以比 (setf fdefinition) 灵活一点,因为它适用于任意函数名,而不仅仅是符号。
  • @JoshuaTaylor:除了符号之外,还有什么函数名?
  • @Matt 一个 (setf something) 形式的列表。你可以,例如,(defun (setf kar) (value kons) ...)
  • @JoshuaTaylor:哇,谢谢!知道这一点非常有用。
  • 请注意,这在许多情况下都有效。例外:编译文件时,编译器可能会内联函数调用。然后不会看到新的定义,除非重新编译调用代码。
【解决方案2】:

以这种方式重新定义函数的典型用例是在调试期间。你的例子表明了这一点。 Common Lisp 已经为此提供了更高级别的机制:TRACE。它在底层设置了符号的函数值,但它提供了更方便的用户界面。通常像TRACE 这样的东西会有很多非标准选项。

追踪功能

下面的例子使用Clozure Common Lisp,它总是在编译代码:

? (defun sum (list accumulator)
    (declare (optimize debug))   ; note the debug declaration
    (if (endp list)
        accumulator
        (sum (rest list) (+ accumulator (first list)))))
SUM
? (sum '(1 2 3 4) 0)
10
? (trace sum)
NIL
? (sum '(1 2 3 4) 0)
0> Calling (SUM (1 2 3 4) 0) 
 1> Calling (SUM (2 3 4) 1) 
  2> Calling (SUM (3 4) 3) 
   3> Calling (SUM (4) 6) 
    4> Calling (SUM NIL 10) 
    <4 SUM returned 10
   <3 SUM returned 10
  <2 SUM returned 10
 <1 SUM returned 10
<0 SUM returned 10
10

然后去追踪:

? (untrace sum)

建议功能

在您的示例中,您在输入函数时打印了参数。在许多 Common Lisp 实现中,还有另一种机制可以通过附加功能来扩充功能:advising。再次使用 Clozure Common Lisp 及其 advise 宏:

? (advise sum                                 ; the name of the function
          (format t "~%Arglist: ~a" arglist)  ; the code
          :when :before)                      ; where to add the code
#<Compiled-function (CCL::ADVISED 'SUM) (Non-Global)  #x302000D7AC6F>

? (sum '(1 2 3 4) 0)

Arglist: ((1 2 3 4) 0)
Arglist: ((2 3 4) 1)
Arglist: ((3 4) 3)
Arglist: ((4) 6)
Arglist: (NIL 10)
10

【讨论】:

  • 哇,感谢您提供的非常有用的提示!附带问题:如果endpnot 相同(或重新排序if 表达式),那么它有什么意义?
  • @Matt: ENDP 表示参数是一个列表。 (endp somelist)(null anything)。使阅读代码变得更好一些,并且当ENDP 被调用时使用列表以外的其他内容时,也可能会发出错误信号。
猜你喜欢
  • 2011-04-27
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 1970-01-01
  • 2017-10-19
  • 2015-12-12
  • 2014-04-26
  • 1970-01-01
相关资源
最近更新 更多