lisp programming

Since we already have block and return macro in elisp (from cl-lib), this post is useless. Also an obviously better implemention of this will use catch-and-throw.

Something I recently realized is that I have been writing a bunch of nested if expressions for reporting error-ish cases in a lot of elisp packages, unchecked. An example follows:

(defun bmp-bump (bmp-type)
  "Bump version using the given bmp-type"
  (let* ((default-directory (projectile-project-root))
         (project (bmp-get-project bmp-project-fns)))
    (if (null project)
        (message "No project detected")
      (if (bmp-git-dirty-p)
          (message "Git repository dirty")
        (if (not (bmp-git-master-p))
            (message "Not on master")
          (let* ((version-str (bmp-get-version project))
                 (new-ver-str (bmp-new-version version-str bmp-type)))
            (bmp-set-version project new-ver-str)
            (bmp-commit (bmp-get-files project) new-ver-str)
            (bmp-tag new-ver-str)))))))

Another gloriously messy example is this one. Cases like these are common in main functions of packages since they have to work on edges.

In langauges like python, these look okay because you can do early return. The combination of that with if (without the elif branching; note that elif behavior is already in cond expressions) gives you a way to bail out while keeping the code nesting in check. bo> macro is a simple attempt to replicate that 1.

(defun bmp-bump (bmp-type)
  "Bump version using the given bmp-type"
  (let* ((default-directory (projectile-project-root))
         (project (bmp-get-project bmp-project-fns)))
    (bo>
     (?? (null project) (message "No project detected"))
     (?? (bmp-git-dirty-p) (message "Git repository dirty"))
     ;; Some thing here
     (?? (not (bmp-git-master-p)) (message "Not on master"))

     (let* ((version-str (bmp-get-version project))
            (new-ver-str (bmp-new-version version-str bmp-type)))
       (bmp-set-version project new-ver-str)
       (bmp-commit (bmp-get-files project) new-ver-str)
       (bmp-tag new-ver-str)))))

Expansion of the macro form gives

(progn
  (if (null project)
      (message "No project detected")
    (progn
      (if (bmp-git-dirty-p)
          (message "Git repository dirty")
        (progn
          (if (not (bmp-git-master-p))
              (message "Not on master")
            (progn
              (let* ((version-str (bmp-get-version project))
                     (new-ver-str (bmp-new-version version-str bmp-type)))
                (bmp-set-version project new-ver-str)
                (bmp-commit (bmp-get-files project) new-ver-str)
                (bmp-tag new-ver-str)))))))))

The extra progn help in cases with multiple forms after a ?? form. The macro itself is short and only works flat.

(defmacro bo> (&rest body)
  `(progn ,@(bo-transform body)))

(defun bo-transform (items)
  (let ((fst (car items)))
    (unless (null fst)
      (if (eq (car fst) ??)
          `((if ,(second fst) ,(third fst)
              (progn ,@(bo-transform (cdr items)))))
        (cons fst (bo-transform (cdr items)))))))

Footnotes:

1

Its basically if and return macro clumped together