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

Cai*_*inG 8 lisp common-lisp

我只是在学习ANSI Common Lisp(在Win32机器上使用clisp),我想知道mapcar是否可以使用传入的函数作为正式参数?请参阅以下内容:

(defun foo (fn seq) 
    (mapcar #'fn seq))
Run Code Online (Sandbox Code Playgroud)

在我看来,这将提供比以下更大的灵活性:

(defun mult (i)
    (* i 2))

(defun foo ()
    (mapcar #'mult '(1 2 3)))
Run Code Online (Sandbox Code Playgroud)

Dan*_*ton 31

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

你的代码几乎是正确的.你写了:

(defun foo (fn seq) 
    (mapcar #'fn seq))
Run Code Online (Sandbox Code Playgroud)

但是,那是什么尝试呢?嗯,#'是速记.我会为你扩展它.

(defun foo (fn seq) 
    (mapcar (function fn) seq))
Run Code Online (Sandbox Code Playgroud)

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

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

现在,在REPL上,你写的内容显然是你想要的.

(mapcar #'car sequence)
Run Code Online (Sandbox Code Playgroud)

将汽车功能映射到一系列列表.并且汽车符号没有变量绑定,只有函数绑定,这就是为什么你需要使用(函数...)(或它的简写,#')来获取实际函数.

你的foo函数不起作用,因为你将它作为参数传递的函数被绑定到一个符号作为变量.试试这个:

(let ((fn #'sqrt))
    (mapcar #'fn '(4 9 16 25)))
Run Code Online (Sandbox Code Playgroud)

您可能已经预期所有这些数字的平方根列表,但它不起作用.那是因为你使用let将平方根函数绑定到fn作为变量.现在,试试这段代码:

(let ((fn #'sqrt))
    (mapcar fn '(4 9 16 25)))
Run Code Online (Sandbox Code Playgroud)

愉快!这将平方根函数绑定到fn符号作为变量.

那么,我们来修改你的foo函数:

(defun foo (fn seq) 
    (mapcar fn seq))
Run Code Online (Sandbox Code Playgroud)

这将起作用,因为fn是一个变量.让我们测试一下,只是为了确保:

;; This will not work
(foo sqrt '(4 9 16 25))
;; This will work
(foo #'sqrt '(4 9 16 25))
Run Code Online (Sandbox Code Playgroud)

第一个不起作用,因为平方根函数在函数名称空间中绑定到sqrt.因此,在第二个中,我们从符号中获取函数,并将其传递给foo,foo将其作为变量绑定到符号fn.


好的,那么如果你想将函数绑定到函数名称空间中的符号呢?好吧,对于初学者来说,defun会永久地做到这一点.如果你想要它是临时的,比如let绑定,请使用flet.在我看来,Flet是愚蠢的,因为它不像let那样起作用.但是,我会给出一个例子,你可以看到.

(flet ((fn (x) (sqrt x)))
    (mapcar fn '(4 9 16 25)))
Run Code Online (Sandbox Code Playgroud)

将无法工作,因为flet没有将函数绑定到变量名称空间中的符号,而是在函数名称空间中.

(flet ((fn (x) (sqrt x)))
    (mapcar #'fn '(4 9 16 25)))
Run Code Online (Sandbox Code Playgroud)

这将完成您的预期,因为flet将该函数绑定到函数名称空间中的符号fn.而且,只是为了推动函数名称空间home的想法:

(flet ((fn (x) (sqrt x)))
    (fn 16))
Run Code Online (Sandbox Code Playgroud)

将返回4.


Chr*_*ung 4

当然,你可以这样做:

(defun foo (fn)
  (mapcar fn '(1 2 3)))
Run Code Online (Sandbox Code Playgroud)

例子:

(foo #'(lambda (x) (* x 2)))
(foo #'1+)
(foo #'sqrt)
(foo #'(lambda (x) (1+ (* x 3))))
Run Code Online (Sandbox Code Playgroud)