'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))...wrapsfn1inlabels.(let+ ((arg2a arg2b) body2))...does amultiple-value-bindonbody2, binding its results toarg2a 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 |
