Ongoing adventures with lisp

  • Can you imagine indenting code as follows in python?
        i=0, j=1
            k, l = 34, 65
                [i, k] = [k, i]
    
    Each of these assignments corresponds to a different special form in Common Lisp:
        (let* ((i 0)
               (j 1))
          (multiple-value-bind (k l) (values 34 65)
            (destructuring-bind (i k) '(k i)
              ...)))
    
    Verbose and visually cluttered. When I realized returning multiple values was turning into such a pain it was turning me off values, I created the bind macro in response:
        (bind (i 0)
              (j 1)
              (:mv (k l) (values 34 65))
              (:db (i k) '(k i))
          :do
              ...)
    
    Much better0,1. Here's how I implement bind:
    (defmacro bind (&rest body)
      (apply 'bind-func body))
    
    (defun bind-func (&rest body)
      (cond ((null (first body)) (error "no :do in bind"))
            ((eq (first body) :do) `(progn ,@(rest body)))
            (t (multiple-value-bind (let-form body)
                   (determine-let-form body)
                 (destructuring-bind ((a b) . body) body
                   (if (eq let-form 'let)
                       `(let ((,a ,b)) ,(apply 'bind-func body))
                     `(,let-form ,a ,b ,(apply 'bind-func body))))))))
    
    (defun determine-let-form (form)
      (destructuring-bind ((a . rest) . body) form
        (cond ((eq a :db) (values 'destructuring-bind (cons rest body)))
              ((eq a :mv) (values 'multiple-value-bind (cons rest body)))
              (t (values 'let form)))))
    

  • Within lisp buffers in emacs I navigate entirely in terms of S-expressions. C-Left and C-Right move 'horizontally' in the parse tree to the previous or next sibling, or up the tree if that's not possible. C-Up and C-Down move 'vertically' in the parse tree to the parent or first child of the sexp under point. Here's how these intuitive keymappings are implemented in my .emacs:
    (defun backward-or-up-sexp ()
      (interactive)
      (if (eq (char-before (point)) ?\()
        (backward-up-list)
        (backward-sexp)))
    
    (defun forward-or-up-sexp ()
      (interactive)
      (if (eq (char-after (point)) ?\))
        (up-list)
        (forward-sexp)))
    
    (defun down-sexp ()
      (interactive)
      (if (member (char-after (point)) '(?\n ?\)))
        (backward-sexp))
      (down-list))
    
    (global-set-key '(control left) 'backward-or-up-sexp)
    (global-set-key '(control right) 'forward-or-up-sexp)
    (global-set-key '(control up) 'backward-up-list)
    (global-set-key '(control down) 'down-sexp)
    

  • ...

  • footnotes

    0. I prefer to use keywords rather than argument index+indentation to highlight semantic structure.
    1. For two syntactic alternatives that don't use the :db and :mv keywords, see Gary King's version, coincidentally also called bind.