3

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?

upgrd
  • 720
  • 7
  • 16

1 Answers1

7

After all trying out, and with some comments by @WillNess (thanks!) I came up with this idea:

macro bind

The 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)

My original answer

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)

The macro first-*rest-last

To 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.

Gwang-Jin Kim
  • 9,303
  • 17
  • 30
  • 1
    @upgrd welcome! Have fun in your lisp journey! :) – Gwang-Jin Kim Dec 09 '20 at 19:42
  • 1
    `(bind (([a, ...bs, c] a_list)) __body__)` would be quite clear, I think. I do mean `[a, ...bs, c]` literally, as a pattern. (or some equivalent). – Will Ness Dec 10 '20 at 08:06
  • @WillNess - I added a further macro definition. Since `.` is reserved `...rest` doesn't work. So I used `_rest`. Is this clojure notation? – Gwang-Jin Kim Dec 10 '20 at 08:35
  • In clojure, one could destructure like this `(let [[[a & b] c] [(butlast [1 2 3 4]) (last [1 2 3 4])]] (list a b c))` – Gwang-Jin Kim Dec 10 '20 at 08:41
  • I think JS, but I'm not sure. I first [dreamt it up](https://stackoverflow.com/search?q=user%3A849891+pseudocode+%5Bscheme%5D+collector) myself, as `[a, ...bs..., c, ...ds...]` etc.. then nixed the trailing dots / saw JS snippets like that on SO, IIRC. – Will Ness Dec 10 '20 at 08:52
  • oh, no, your macro is not general at all -- `[a, b, ...cs]` is also a valid pattern! :) – Will Ness Dec 10 '20 at 09:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/225770/discussion-between-gwang-jin-kim-and-will-ness). – Gwang-Jin Kim Dec 10 '20 at 09:51
  • But in common lisp I see that `...cs` gets error messages with `..cs` so the first dot might be interpreted as `.`? – Gwang-Jin Kim Dec 10 '20 at 09:52
  • It's just a pseudocode. I never tried implementing it in any language. (maybe `,@` could work, in CL?.... dunno). – Will Ness Dec 10 '20 at 10:26
  • @WillNess - No, as far as I know not. `destructuring-bind` works with lambda-lists. And lambda-lists have no way to capture only the last element. They can only take the rest `&rest` of the list as a list. To implement `...`, one has to write reader-macros, I guess which then underneath work with either lambdas (`let`, `destructuring-bind` or `lambda` itself). – Gwang-Jin Kim Dec 10 '20 at 10:38
  • As far as I have seen, the package `bind` https://www.cliki.net/bind is the best combination of a destructuring `let*` that comes closest to clojure's `let`. However, clojure uses instead of labmda-lists vectors and destructuring in clojure works only on those vector constructs. While common lisp destructures on (lambda) lists (those lists which take the function arguments) supporting all the syntax of those. And things like javascripts's `...` or Python's `*`, `**` are probably not impossible to implement, but one has to be quite black belt in macro programming to be able to do that. – Gwang-Jin Kim Dec 10 '20 at 10:42