The Ret Macro

The ret macro is simple, but useful. It allows you to bind a variable and simultaneously specify the final value of that variable as the result of the ret expression, hence the name “ret”, a blend of let and return. Here is the implementation of ret:

(defmacro ret (var val &body body)
  `(let ((,var ,val))
     ,@body
     ,var))

And now for an example. If you want to write a function that converts a hash-table into an alist, here is how you could do that using ret.

(defun table->alist (table)
  (ret result '()
    (maphash (lambda (k v)
               (push (cons k v) result))
             table)))

Personally, I find code using ret to be much clearer than the equivalent code using let. When writing the let version, I commonly find myself forgetting to return the value I want to at the end. Ret eliminates this problem by returning the result for me. The only downside I see to using ret is that it has the same problem as all of the “do” macros (dotimes, dolist, etc), they change where the resulting value comes from. Even so, this problem is much more mild with ret because ret will always change the control flow, whereas the “do” macros may or may not change the final value depending on the number of arguments passed to them.

2 thoughts on “The Ret Macro

  1. I thought about generalizing your macro to handle an arbitrary number of bindings, so that you could give it e.g. 3 bindings and after running the body it would return those 3 bindings as values. Here’s what I came up with:

    (defmacro multi-ret ((&rest bindings) &body body)
    (let ((vars (loop for (v nil) on bindings by #’cddr collect v)))
    `(let (,@(loop for (var val) on bindings by #’cddr
    collect (list var val)))
    ,@body
    (funcall #’values ,@vars))))

    Example of usage:
    (multi-ret (a 1 b ‘())
    (loop repeat 5
    do
    (push a b)
    (incf a)))
    > 6
    > (5 4 3 2 1)

Leave a Reply

Your email address will not be published. Required fields are marked *