Common Lisp:在first、rest、last中对列表进行解构(类似于Python的可迭代解包)



David Touretzky的《公共Lisp》一书中的练习6.36要求使用一个函数swap-first-last来交换任何列表的第一个和最后一个参数。我现在觉得很愚蠢,但我无法用destructuring-bind解决这个问题。

在Python中,如何在Common Lisp/withdestructuring-bind中实现first, *rest, last = (1,2,3,4)(可迭代拆包(?

经过所有的尝试,加上@WillNess的一些评论(谢谢!(,我想出了这个主意:

bind

这个想法是试图细分列表,并在destructuring-bind中使用lambda列表的&rest功能,然而,使用较短的.表示法,并使用butlastcar-last组合。

(defmacro bind ((first _rest last) expr &body body)
`(destructuring-bind ((,first . ,_rest) ,last) 
`(,,(butlast expr) ,,(car (last expr)))
,@body)))

用法:

(bind (f _rest l) (list 1 2 3 4) 
(list f _rest l))
;; => (1 (2 3) 4)

我最初的答案

没有比Python更优雅的可能性了。destructuring-bind的绑定方式与lambda的绑定方式不同:lambda列表只将其余部分作为&rest <name-for-rest>。没有办法直接取出最后一个元素。(当然,没有办法,除非你为这类问题额外写一个宏(。

(destructuring-bind (first &rest rest) (list 1 2 3 4)
(let* ((last (car (last rest)))
(*rest (butlast rest)))
(list first *rest last)))
;;=> (1 (2 3) 4)
;; or:
(destructuring-bind (first . rest) (list 1 2 3 4)
(let* ((last (car (last rest)))
(*rest (butlast rest)))
(list first *rest last)))

但当然,如果你是lisp,理论上你可以编写宏destructuring-bind以一种更复杂的方式。。。

但是,destructuring-bind不会比更清晰

(defparameter *l* '(1 2 3 4))
(let ((first (car *l*))
(*rest (butlast (cdr *l*)))
(last (car (last *l*))))
(list first *rest last))
;;=> (1 (2 3) 4)

first-*rest-last

为了向您展示,在常见的lisp中,这样一个宏的生成速度有多快:

;; first-*rest-last is a macro which destructures list for their 
;; first, middle and last elements.
;; I guess more skilled lisp programmers could write you
;; kind of a more generalized `destructuring-bind` with some extra syntax ;; that can distinguish the middle pieces like `*rest` from `&rest rest`.
;; But I don't know reader macros that well yet.
(ql:quickload :alexandria)
(defmacro first-*rest-last ((first *rest last) expr &body body)
(let ((rest))
(alexandria:once-only (rest)
`(destructuring-bind (,first . ,rest) ,expr
(destructuring-bind (,last . ,*rest) (nreverse ,rest)
(let ((,*rest (nreverse ,*rest)))
,@body))))))
;; or an easier definition:
(defmacro first-*rest-last ((first *rest last) expr &body body)
(alexandria:once-only (expr)
`(let ((,first (car ,expr))
(,*rest (butlast (cdr ,expr)))
(,last (car (last ,expr))))
,@body))))

用法:

;; you give in the list after `first-*rest-last` the name of the variables
;; which should capture the first, middle and last part of your list-giving expression
;; which you then can use in the body.
(first-*rest-last (a b c) (list 1 2 3 4)
(list a b c))
;;=> (1 (2 3) 4)

此宏允许您为列表的first*restlast部分提供任何名称,您可以在宏的主体中进一步处理这些名称,希望有助于提高代码的可读性。

最新更新