Exercise 6.36 of David Touretzky's Common Lisp book asks for a function swap-first-last that swaps the first and last argument of any list. I feel really stupid right now, but I am unable to solve this with destructuring-bind.
How can I do what in Python would be first, *rest, last = (1,2,3,4) (iterable unpacking) in Common Lisp/with destructuring-bind?
After all trying out, and with some comments by @WillNess (thanks!) I came up with this idea:
bindThe idea is trying to subdivide the list and use the &rest functionality of the lambda list in destructuring-bind, however, using the shorter . notation - and using butlast and the car-last combination.
(defmacro bind ((first _rest last) expr &body body)
`(destructuring-bind ((,first . ,_rest) ,last)
`(,,(butlast expr) ,,(car (last expr)))
,@body)))
usage:
(bind (f _rest l) (list 1 2 3 4)
(list f _rest l))
;; => (1 (2 3) 4)
There is no so elegant possibility like for Python.
destructuring-bind cannot bind more differently than lambda can: lambda-lists take only the entire rest as &rest <name-for-rest>.
No way there to take the last element out directly.
(Of course, no way, except you write a macro extra for this kind of problems).
(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)))
But of course, you are in lisp, you could theoretically write macros to
destructuring-bind in a more sophisticated way ...
But then, destructuring-bind does not lead to much more clarity than:
(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-lastTo show you, how quickly in common lisp such a macro is generated:
;; 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))))
Usage:
;; 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)
This macro allows you to give any name for the first, *rest and last part of the list, which you can process further in the body of the macro,
hopefully contributing to more readability in your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With