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

时间:2012-01-08 03:39:23

标签: lisp common-lisp

我只是在学习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 个答案:

答案 0 :(得分:31)

绝对可能!你快到了。你刚刚碰到了Common Lisp的双重命名空间,这可能需要很多人习惯。我希望我可以说一两件事让Common Lisp的两个命名空间变得更加混乱。

您的代码几乎是正确的。你写道:

(defun foo (fn seq) 
    (mapcar #'fn seq))

但是,那是做什么的?好吧,#'是简写。我会为你扩展它。

(defun foo (fn seq) 
    (mapcar (function fn) seq))

因此,#'符号(功能符号)的简写。在Common Lisp中 - 您似乎知道 - 符号可以绑定到函数和变量;这些是Lispers所说的两个名称空间:函数名称空间和变量名称空间。

现在,函数特殊形式的作用是将函数绑定到符号,或者,如果您愿意,还可以获取符号在函数名称空间中的值。

现在,在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,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 。而且,只是为了推动函数名称空间home的想法:

(flet ((fn (x) (sqrt x)))
    (fn 16))

将返回4.

答案 1 :(得分:3)

当然,你可以这样做:

(defun foo (fn)
  (mapcar fn '(1 2 3)))

示例:

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