Search code examples
lispcommon-lispcl

Common Lisp: Destructure a list in first, rest, last (like Python iterable unpacking)


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?


Solution

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