'How to format a nested multiple-value-bind the let way?

Recently, I've been often nesting several functions that return multiple values. However, unlike let, which me allows to write these calls elegantly into one big statement, I always end up with a lot of indentation.

My question is: having several multiple-valued functions such as

(defun return-1-and-2 ()
  (values 1 2))

(defun return-3-and-4 ()
  (values 3 4))

is it possible to achieve the same as

(multiple-value-bind (one two)
    (return-1-and-2)
  (multiple-value-bind (three four)
      (return-3-and-4)
    (list one two three four)))

but write it more concisely the let-way, i.e., something like

(multiple-let (((one two) (return-1-and-2))
               ((three four) (return-3-and-4)))
  (list one two three four))

?



Solution 1:[1]

I have grown a bit fond of the library let-plus, which offers a let+ macro that has this option (among others):

(let+ (((&values one two) (return-1-and-2))
       ((&values three four) (return-3-and-4))
       (foo (bar))                  ; other examples
       (#(a b c) (some-vector)))    ;
  #| body… |#)

Solution 2:[2]

In Serapeum, mvlet*:

Expand a series of nested multiple-value-bind forms.

  (mvlet* ((minutes seconds (truncate seconds 60))
           (hours minutes (truncate minutes 60))
           (days hours (truncate hours 24)))
    (declare ((integer 0 *) days hours minutes seconds))
    (fmt "~d day~:p, ~d hour~:p, ~d minute~:p, ~d second~:p"
         days hours minutes seconds))

https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#mvlet-rest-bindings-body-body

Solution 3:[3]

I extended the above to allow let to handle not just multiple-value-bind but also labels. My code is a little simpler than serapeum's since they handle more cool cases than me. For example in serapeum's code, if a let+ has no special features, it expands to a normal let*. By ignoring those cool features, I get to write it all in just a dozen lines:

(defun %let+ (body xs)
  (labels ((fun (x) (and (listp x) (> (length x) 2)))
           (mvb (x) (and (listp x) (listp (car x)))))
    (if (null xs)
      body
      (let ((x (pop xs)))
        (cond
          ((fun x) `(labels ((,(pop x) ,(pop x) ,@x))       ,(%let+ body xs)))
          ((mvb x) `(multiple-value-bind ,(pop x) ,(pop x) ,@(%let+ body xs)))
          (t       `(let (,x)                          ,(%let+ body xs))))))))

(defmacro let+ (spec &rest body) (%let+ body spec))

In this let+ macro...

  • (let+ (x (y 1))... expands as normal
  • (let+ ((fn1 (arg1 arg1b) body1))... wraps fn1 in labels.
  • (let+ ((arg2a arg2b) body2))... does a multiple-value-bind on body2, binding its results to arg2a arg2b.

Example:

(defun fn2 (x y ) (values x (+ x y)))

(defun test-let+(&optional (x 1))
  (let+ (z                          ; normal let stuff
         (y 1)                      ; normal let stuff
         (z 2)                      ; normal let stuff
         (fn1 (x y) (+ x y))        ; define a local function
         ((a b) (fn2 x (fn1 y z)))) ; call multiple-value-bind
      (format t "~&a ~a b ~a x ~a y ~a z ~a~%" a b x y z)))

Which expands into this:

(DEFUN TEST-LET+ (&OPTIONAL (X 1))
  (LET (Z)
    (LET ((Y 1))
      (LET ((Z 2))
        (LABELS ((FN1 (X Y)
                   (+ X Y)))
          (MULTIPLE-VALUE-BIND (A B)
              (FN2 X (FN1 Y Z))
            (FORMAT T "a ~a b ~a x ~a y ~a z ~a~%" A B X Y Z)))))))

And runs like this....

> (test-let+) 

a 1 b 4 x 1 y 1 z 2

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Svante
Solution 2 Ehvince
Solution 3