我正在学习ANSI Common Lisp(在Win32机器上使用clisp),我想知道mapcar是否可以使用作为形式参数传递的函数?请参见以下内容:
(defun foo (fn seq)
(mapcar #'fn seq))
在我看来,这将比以下做法提供更高的灵活性:
(defun mult (i)
(* i 2))
(defun foo ()
(mapcar #'mult '(1 2 3)))
我正在学习ANSI Common Lisp(在Win32机器上使用clisp),我想知道mapcar是否可以使用作为形式参数传递的函数?请参见以下内容:
(defun foo (fn seq)
(mapcar #'fn seq))
在我看来,这将比以下做法提供更高的灵活性:
(defun mult (i)
(* i 2))
(defun foo ()
(mapcar #'mult '(1 2 3)))
这绝对是可以实现的!你已经很接近了。你只是遇到了Common Lisp的双重命名空间,这可能需要一些时间来适应。我希望我能说几句话,让Common Lisp的两个命名空间变得更加清晰。
你的代码几乎是正确的。你写道:
(defun foo (fn seq)
(mapcar #'fn seq))
不过,这是在做什么呢?嗯,#' 是速记法。我会为您展开它。
(defun foo (fn seq)
(mapcar (function fn) seq))
所以,#'symbol是(function symbol)的速记。在Common Lisp中(正如您所知),符号可以绑定到函数和变量;这些是Lisper经常谈论的两个命名空间:函数命名空间和变量命名空间。
现在,function特殊形式所做的是获取绑定到符号的函数,或者说,在函数命名空间中符号具有的值。
现在,在REPL中,您编写的内容显然就是您想要的。
(mapcar #'car sequence)
将函数car映射到一个列表序列中。而car符号没有变量绑定,只有函数绑定,这就是为什么你需要使用(function ...)(或其简写形式#')来获取实际的函数。(let ((fn #'sqrt))
(mapcar #'fn '(4 9 16 25)))
你可能希望得到所有这些数字的平方根列表,但它没有起作用。这是因为您使用 let 将平方根函数绑定到变量 fn 上。现在,请尝试这段代码:
(let ((fn #'sqrt))
(mapcar fn '(4 9 16 25)))
太好了!这将平方根函数绑定到fn符号作为变量。
那么,让我们修改你的foo函数:
(defun foo (fn seq)
(mapcar fn seq))
fn是一个变量,所以那样做会起作用。让我们进行测试,以确保它:
;; This will not work
(foo sqrt '(4 9 16 25))
;; This will work
(foo #'sqrt '(4 9 16 25))
第一个方法不起作用,因为平方根函数被绑定到函数命名空间中的sqrt。所以,在第二个方法中,我们从符号中获取该函数,并将其传递给foo,它将其绑定到符号fn作为变量。
好的,那么如果您想将一个函数绑定到函数命名空间中的符号上呢?嗯,首先,defun会永久地绑定它。如果您希望它像let一样是暂时性的绑定,请使用flet。在我看来,flet有点愚蠢,因为它的工作方式并不完全像let。但我会给出一个示例,这样您就可以看到。
(flet ((fn (x) (sqrt x)))
(mapcar fn '(4 9 16 25)))
无法正常工作,因为 flet 没有将函数绑定到变量命名空间中的符号,而是绑定到函数命名空间中。
(flet ((fn (x) (sqrt x)))
(mapcar #'fn '(4 9 16 25)))
这将会按照您的期望运行,因为flet将该函数绑定到了函数命名空间中的符号fn。另外,为了更好地理解函数命名空间的概念:
(flet ((fn (x) (sqrt x)))
(fn 16))
将返回4。
(defun foo (fn)
(mapcar fn '(1 2 3)))
例子:
(foo #'(lambda (x) (* x 2)))
(foo #'1+)
(foo #'sqrt)
(foo #'(lambda (x) (1+ (* x 3))))