使用Lisp进行高阶编程:如何将函数传递给mapcar?

14

我正在学习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)))
2个回答

37

这绝对是可以实现的!你已经很接近了。你只是遇到了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 ...)(或其简写形式#')来获取实际的函数。
你的foo函数不起作用,因为你传递给它的函数被绑定到一个符号作为变量。试试这个:
(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。


4
当然,您可以这样做:
(defun foo (fn)
  (mapcar fn '(1 2 3)))

例子:

(foo #'(lambda (x) (* x 2)))
(foo #'1+)
(foo #'sqrt)
(foo #'(lambda (x) (1+ (* x 3))))

很好的简短回答。 - Kepler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接