How to return multiple values iteratively in Common Lisp

Viewed 172

To return multiple values, one could use (values v1 v2 v3)

How does one return multiple values iteratively from a do loop? In other words, I want to build the values of the multiple value return in a do loop, but I can only construct a list. I know of values-list, but the point of using multiple values is to not construct a list.

Is there any way of returning multiple values iteratively without constructing a list?

2 Answers

If your Common Lisp implementation supports stack allocation, then this would be one way to deal with that.

For example in LispWorks this is possible:

(defun foo (list)
  (let ((vlist (make-list (length list)
                          :initial-element nil)))
    (declare (dynamic-extent vlist))              ; allocate VLIST on the stack
    (values-list (map-into vlist #'1+ list))))    ; return VLIST as values

In similar ways one could use DO to set the stack allocated list.

LispWorks recognizes MAKE-LIST for stack allocation in combination with the dynamic-extent declaration. SBCL does not, but could use something like LIST for stack allocation.

As I said in a comment: if you don't know how many values you want, then you probably should not be using multiple values: since the limit can be as low as 20 you may hit that limit, and any receiver will need to convert the values into a data structure to know how many there are. Simply returning the data structure is going to be better.

So I will assume that you do know how many values you want. In that case you can just bind a variable for each value. But no-one wants to write code like that, of course. Well, this is Lisp: you don't have to write boring repetitive code!

(defmacro with-indexed-values ((accessor n &optional (initial-value 'nil))
                               &body decls/forms)
  "Evaluate DECLS/FORMS with N indexed values: (ACCESSOR i) will
retrieve the ith value, and (SETF (ACCESSOR I) v) will set it Indices
run from 0.  All of the indexed values are returned as multiple
values.  INITIAL-VALUE, if given is the initial value for the values:
it is evaluated once."
  (unless (<= n multiple-values-limit)
    (error "~A is too many values: limit is ~A" n multiple-values-limit))
  
  (let ((varnames (loop for i below n
                        collect (make-symbol (format nil "V~D" i))))
        (ivname (make-symbol "IV")))
    `(let ((,ivname ,initial-value))
       (let ,(loop for v in varnames collect `(,v ,ivname))
         (flet ((,accessor (i)
                  (case i
                    ,@(loop for v in varnames
                            for i upfrom 0
                            collect `(,i ,v))
                    (otherwise
                     (error "~A isnt an integer in [0, ~A)" i ,n))))
                ((setf ,accessor) (new i)
                  (case i
                    ,@(loop for v in varnames
                            for i upfrom 0
                            collect `(,i (setf ,v new)))
                    (otherwise
                     (error "~A isnt an integer in [0, ~A)" i ,n)))))
           (declare (inline ,accessor (setf ,accessor)))
           ,@decls/forms
           (values ,@varnames))))))

And now:

> (with-indexed-values (v 2)
    (setf (v 0) 2)
    (setf (v 1) (+ (v 0) 1))
    (decf (v 0)))
1
3

Or

 > (with-indexed-values (v 5 (cons nil nil))
     (loop for i below 3
           do (setf (v i) i)))
0
1
2
(nil)
(nil)

(And the two last values are eq.)

Here is another version of the same macro: this one stashes the values in a vector, and relies on the vector being stack-allocated. LispWorks can do this: I have not checked other implementations. This one has the advantage that the accessors are simply array references and should be fast, but the disadvantage that it relies on the implementation stack-allocating the vector.

(defmacro with-indexed-values ((accessor n &optional (initial-value 'nil))
                               &body decls/forms)
  "Evaluate DECLS/FORMS with N indexed values: (ACCESSOR i) will
retrieve the ith value, and (SETF (ACCESSOR I) v) will set it Indices
run from 0.  All of the indexed values are returned as multiple
values.  INITIAL-VALUE, if given is the initial value for the values:
it is evaluated once."
  (unless (<= n multiple-values-limit)
    (error "~A is too many values: limit is ~A" n multiple-values-limit))
  (let ((vname (make-symbol "V")))
    `(let ((,vname
            (let ((init ,initial-value))
              (vector ,@(loop repeat n collect 'init)))))
       (declare (dynamic-extent ,vname))
       (flet ((,accessor (i)
                (aref ,vname i))
              ((setf ,accessor) (new i)
                (setf (aref ,vname i) new)))
         (declare (inline ,accessor (setf ,accessor)))
         ,@decls/forms
         (values ,@(loop for i below n
                         collect `(aref ,vname ,i)))))))
Related