有没有一种方法可以在Common Lisp中实现mapcar,只使用应用程序编程,避免递归或迭代作为编程风格



我正试图通过《Common Lisp:符号计算入门》一书来学习Common Lisp。此外,我正在使用SBCL、Emacs和Slime。

在第7章中,作者建议本书将涵盖三种编程风格:递归、迭代和应用编程。

我对最后一个感兴趣。这种风格以应用运算符funcall而闻名,它是负责其他应用运算符(如mapcar(的基元。

因此,出于教育目的,我决定使用funcall:实现我自己版本的mapcar

(defun my-mapcar (fn xs)
(if (null xs)
nil
(cons (funcall fn (car xs))
(my-mapcar fn (cdr xs)))))

正如您可能看到的,我使用递归作为一种编程风格来构建一个标志性的应用程序编程函数。

它似乎起作用:

CL-USER> (my-mapcar (lambda (n) (+ n 1)) (list 1 2 3 4))
(2 3 4 5)
CL-USER> (my-mapcar (lambda (n) (+ n 1)) (list ))
NIL
;; comparing the results with the official one
CL-USER> (mapcar (lambda (n) (+ n 1)) (list ))
NIL
CL-USER> (mapcar (lambda (n) (+ n 1)) (list 1 2 3 4))
(2 3 4 5)

有没有一种方法可以在不使用递归或迭代的情况下实现mapcar?只使用应用性编程作为一种风格?

谢谢。

Obs.:我试着看看它是如何实现的。但是不可能的

CL-USER> (function-lambda-expression #'mapcar)
NIL
T
MAPCAR

我还使用EmacsM-.来查找文档。然而,以下几点对我没有帮助。我用它找到了以下文件:

/usr/share/sbcl-source/src/code/list.lisp
(DEFUN MAPCAR)
/usr/share/sbcl-source/src/compiler/seqtran.lisp
(:DEFINE-SOURCE-TRANSFORM MAPCAR)
/usr/share/sbcl-source/src/compiler/fndb.lisp
(DECLAIM MAPCAR SB-C:DEFKNOWN)

mapcar本身就是一个基元应用运算符(Common Lisp:符号计算的温和介绍,第220页(。因此,如果你想以应用的方式重写它,你应该使用其他一些基元应用运算符,例如mapmap-into。例如,使用map-into:

CL-USER> (defun my-mapcar (fn list &rest lists)
(apply #'map-into (make-list (length list)) fn list lists))
MY-MAPCAR
CL-USER> (my-mapcar #'1+ '(1 2 3))
(2 3 4)
CL-USER> (my-mapcar #'+ '(1 2 3) '(10 20 30) '(100 200 300))
(111 222 333)

从技术上讲,递归可以实现如下:

(defun fix (f)
(funcall (lambda (x) (funcall x x))
(lambda (x) (funcall f (lambda (&rest y) (apply (funcall x x) y))))))

请注意,fix没有以任何方式使用递归。事实上,我们本可以在f的定义中只使用lambda,如下所示:

(defconstant fix-combinator 
(lambda (g) (funcall 
(lambda (x) (funcall x x))
(lambda (x) (funcall 
g 
(lambda (&rest y) (apply (funcall x x) 
y)))))))
(defun fix-2 (f)
(funcall fix-combinator f))

fix-combinator常数通常被称为y组合子。

结果表明,fix具有以下性质:

评估CCD_ 15相当于评估(apply (funcall f (fix f)) list)。非正式地,我们有(fix f) = (funcall f (fix f))

因此,我们可以通过定义map-car(我使用不同的名称来避免包锁定(

(defun map-car (func lst)
(funcall (fix (lambda (map-func) (lambda (lst) ; We want mapfunc to be (lambda (lst) (mapcar func lst))
(if (endp lst) 
nil 
(cons (funcall func (car lst)) 
(funcall map-func (cdr lst)))))))
lst))

请注意缺少递归或迭代。

也就是说,通常mapcar在使用";适用的";编程风格。

实现mapcar的另一种方法是使用更通用的reduce函数(也称为fold(。让我们将用户提供的函数命名为f,并定义my-mapcar

reduce函数携带一个累加器值,用于构建结果列表,这里它将取一个值v,一个子列表rest,并用(funcall f v)rest调用cons,以构建列表。

更准确地说,这里reduce将实现右折叠,因为cons是右关联的(例如,递归列表是"右侧",即cons的第二个自变量,例如(cons a (cons b (cons nil)))(。

为了用reduce定义右折叠,您可以传递:from-end t,这表示它从最后一个元素和初始累加器构建一个值以获得新的累加器值,然后用该新累加器从倒数第二个元素构建新的累加器,等等。这就是确保生成的元素与输入列表的顺序相同的方法。

在这种情况下,reducting函数将其当前元素作为第一个参数,将累加器作为第二个参数。

由于元素的类型和累加器的类型不同,因此需要为累加器传递:initial-value(从列表中获取初始值的默认行为适用于+*等函数,其中累加器与列表元素位于同一域中(。

考虑到这一点,你可以写如下:

(defun my-map (f list)
(reduce (lambda (v rest) (cons (funcall f v) rest))
list
:from-end t
:initial-value nil))

例如:

(my-map #'prin1-to-string '(0 1 2 3))
; => ("0" "1" "2" "3")

最新更新