Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

multiple-values and `let'

305 views
Skip to first unread message

David Bakhash

unread,
Jul 27, 1998, 3:00:00 AM7/27/98
to
hi,

I have the following problem. I have a function which returns 3
values, and I need them all. Let's suppose that the function (we'll
call it "g") returns the sqrt, square, and cube of its argument. And
so I have this let* statement that wants to look like this:

(defun f (x)
(let* ((y (* x 5))
(a (nth-value 0 (g y)))
(b (nth-value 1 (g y)))
(c (nth-value 2 (g y))))
(list a b c)))

supposing that the function "g" is expensive, I don't want to evaluate
it a lot. So what then do I do? How can I do the equivalent of a
`multiple-value-bind' in the middle of a let? Or do I have to put the
`multiple-value-bind' after the whole thing, etc?

dave

Kent M Pitman

unread,
Jul 27, 1998, 3:00:00 AM7/27/98
to
David Bakhash <ca...@bu.edu> writes:

Funny I should find the need to mention Richard Stallman twice in one day.
On the whole, while he's done a number of technically interesting things,
he's not a prticular idol of mine. Nonetheless, credit where credit is due.

Some time back, I'm pretty sure it was Richard suggested at a rather
critical time in history that LET had gone the wrong way. I recall a
time in the past, which must have been Maclisp, where it had
"destructuring". That is, one could do:
(let (((a b) '(1 2))) (list b a)) => (2 1)
It doesn't do that any more, but it did at the time, I think, and he
thought it was wrong. The reason it's probably still relevant is that
you can see vestiges of this in DEFMACRO, which does detructuring of
the same kind for you. Now, you might claim that this is very convenient
because it allows one to exactly match a form's body, as in
(defmacro mydolist ((var val) &body forms)
but it has a specific disadvantage, in that it doesn't encourage
abstraction. There was an alternate "evolutionary path" in the design
that was killed off probably because it just came too late, and it looked
like this:
(let (((list a b) '(1 2))) (list b a)) => (2 1)
To which you might say "ick" (which may also be why it died), but consider
that it was more like SETF. The defmacro-style destructuring could not
support abstractions like the setf-style one could. Consider:
(let ((`(,a ,b) '(1 2))) (cons a b))
OR
(defmacro make-foo (x y) `(,x ,y))
(defun frob-foo (the-foo)
(let (((make-foo a b) the-foo)) (make-foo b a)))
where I don't know the expansion of make-foo (but the compiler does).
OR
(let (((values q r) (truncate x y))) (list q r))
which is the one you were asking about.

Basically, if you ever used a non-list for LET var, I think Maclisp
used to autoload the destructuring-style DEFMACRO, but some had
advocated the setf-style instead.

And for what it's worth, people talked about
(let (((format nil "DEFINE-~A" x) 'define-frob)) x) => "FROB"
but to my knowledge no one implemented that for fear of
(let (((format nil "~A~A" x y) "FOOBAR")) (list x y)) => ??
Nevertheless, it promoted some interesting thought.

You might find it an interesting exercise to program this up and share it.
You could use tfb's import/export defpackage to make your own dialect
that shadowed LET and LET* but left everything else alone.

You can of course just do
(let ((a 1)) (multiple-value-bind (b c) (foo) (let ((d 4) (e 5)) ...)))
instead of
(let ((a 1) ((values b c) (foo)) (d 4) (e 5)) ...)
and you won't need much special to do it.

Or, if you are a victim of evaluation order and really need parallel bind
such that binding a would spoil b, c, or d's ability to compute a
value right, try:
(let ((a 1) (sometemp (multiple-value-list (foo))) (d 4) (e 5))
(let ((b (pop sometemp)) (c (pop sometemp))) ...))

Or you could try
(let (b c)
(let ((a 1)
(for-effect (multiple-value-setq (b c) (foo)))
(d 4)
(e 5))
(declare (ignore for-effect))
...))
which doesn't cons like MULTIPLE-VALUE-LIST does.

If you do it a lot, you could even do
(proclaim '(special *for-effect*))
and then you wouldn't have to put the declaration in places. Consider:
(let (b c)
(let ((a 1) (*for-effect* ...) (d 4) (e 5)) ...)

I've always been amused by the relationship in meaning between
declaring something IGNORE and declaring it SPECIAL. I once had a
system I was converting for a list that didn't have an IGNORE
declaration and I cheated by defining IGNORE to be a synonym for
SPECIAL. That has the additional cool property that in the debugger
you can see the values of the IGNORE variables. You may think it's a
hack that such a declaration works, but really it's IGNORABLE that it
implements, not IGNORE. But that's intrinsic to the whole notion of
special variables, which is that you may need their value elsewhere,
not lexically visible. So you have to not warn about them.

Ah well. Enough fun for one day.
--Kent

Raymond Toy

unread,
Jul 27, 1998, 3:00:00 AM7/27/98
to
>>>>> "David" == David Bakhash <ca...@bu.edu> writes:

David> hi,
David> I have the following problem. I have a function which returns 3
David> values, and I need them all. Let's suppose that the function (we'll
David> call it "g") returns the sqrt, square, and cube of its argument. And
David> so I have this let* statement that wants to look like this:

David> (defun f (x)
David> (let* ((y (* x 5))
David> (a (nth-value 0 (g y)))
David> (b (nth-value 1 (g y)))
David> (c (nth-value 2 (g y))))
David> (list a b c)))

David> supposing that the function "g" is expensive, I don't want to evaluate
David> it a lot. So what then do I do? How can I do the equivalent of a
David> `multiple-value-bind' in the middle of a let? Or do I have to put the
David> `multiple-value-bind' after the whole thing, etc?

What's wrong with

(defun f (x)
(let* ((y (* x 5)))
(multiple-value-bind (a b c)
(g y)
(list a b c))))

Ray

David Bakhash

unread,
Jul 27, 1998, 3:00:00 AM7/27/98
to
Raymond Toy <t...@rtp.ericsson.se> writes:

> What's wrong with
>
> (defun f (x)
> (let* ((y (* x 5)))
> (multiple-value-bind (a b c)
> (g y)
> (list a b c))))

nothing at all. I knew it was an option, but I wanted to fit all the
stuff in the let statement. basically, I wanted descructuring:

Raymond Toy <t...@rtp.ericsson.se> writes:

(defun f (x)
(let* ((y (* x 5))

(values a b c) (g y))
(list a b c)))

you guys really rule--all of you. I loved reading the
responses--especially. Kent's. This thread will be ticked for a long
time.

thanks, all
dave

Erik Naggum

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
* David Bakhash [slightly edited]
| How can I do the equivalent of a MULTIPLE-VALUE-BIND in the middle of a LET?

one option is to use Marco Antoniotti's LETBIND. I picked it up from
somewhere 1994-12-27, and have used it on a few occasions when I got
seriously annoyed with LET, MULTIPLE-VALUE-BIND, and DESTRUCTURING-BIND,
which I guess is also why Marco wrote it to begin with.

unfortunately, I don't remember from where I picked it up, but you can
pick it up from me at <URL:http://src.naggum.no/erik/lisp/letbind.tar.gz>
or <URL:ftp://ftp.naggum.no/lisp/letbind.tar.gz>, whichever you prefer.
(the telephone company is supposed to be working on repairing my line
today, so if the hosts are unreachable, try again later or tomorrow.)

#:Erik
--
http://www.naggum.no/spam.html is about my spam protection scheme and how
to guarantee that you reach me. in brief: if you reply to a news article
of mine, be sure to include an In-Reply-To or References header with the
message-ID of that message in it. otherwise, you need to read that page.

Erik Naggum

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
* Kent M Pitman

| There was an alternate "evolutionary path" in the design that was killed
| off probably because it just came too late, and it looked like this:
| (let (((list a b) '(1 2))) (list b a)) => (2 1)
| To which you might say "ick" (which may also be why it died), but
| consider that it was more like SETF. The defmacro-style destructuring
| could not support abstractions like the setf-style one could. Consider:
| (let ((`(,a ,b) '(1 2))) (cons a b))
| OR
| (defmacro make-foo (x y) `(,x ,y))
| (defun frob-foo (the-foo)
| (let (((make-foo a b) the-foo)) (make-foo b a)))
| where I don't know the expansion of make-foo (but the compiler does).
| OR
| (let (((values q r) (truncate x y))) (list q r))
| which is the one you were asking about.

there appears to have been several experiments with LET some years ago,
but not very much of it seems to have survived. in addition to Marco
Antoniotti's LETBIND, I came across many versions of LETF about the same
time (1994-10). LETF binds places like SETF. although various versions
of LETF float around, I haven't found one that does multiple values right
and the version I hacked to handle it doesn't bind new variables, it only
deals with existing bindings. that may or may not be the right thing, of
course. is there a "definitive" LETF somewhere?

Kent M Pitman

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
Erik Naggum <cle...@naggum.no> writes:

> there appears to have been several experiments with LET some years ago,
> but not very much of it seems to have survived. in addition to Marco
> Antoniotti's LETBIND, I came across many versions of LETF about the same
> time (1994-10). LETF binds places like SETF. although various versions
> of LETF float around, I haven't found one that does multiple values right
> and the version I hacked to handle it doesn't bind new variables, it only
> deals with existing bindings. that may or may not be the right thing, of
> course. is there a "definitive" LETF somewhere?

I remember things like this having floated by, but never used them
much. The main problem is multitasking. I think the Lisp Machine had
the only correct implementation of this I've eve rseen. The T
language (Yale Scheme) had the capability to do it right, but I don't
know if anyone implemented it; Schemers are mostly side-effect averse.
As a rule, most dialects of Lisp and Scheme don't have it, I think.

To see the issue, you need to refer to an obscure paper by Steele
called "Macaroni is Better than Spaghetti" in which he observes that
there are two kinds of UNWIND-PROTECTs needed. (As I recall, the paper
was about how deep spaghetti stacks were bad but how lexical binding
with little teeny threaded stacks were good... or some such thing.
Touting the virtues of lexical binding and all. I don't remember it
for its content, but for its reference to this operator.)

The Maclisp family has only one. The Lisp Machine had primitive
support for many specialized kinds of the second. One winds upon
entry and unwinds on exit (a la WITH-OPEN-FILE). THe other winds upon
entry, unwinds on stack group swap, rewinds on reentry to the stack
group, and so on, finally unwinding at the end. The only vestige of
this in most systems is indeed special variable bindings. In the
LispM, you could do a locative. And in T you didn't have locatives
but you could do the fully general thing of specifying a thunk
(function to be called).

The problem is that if I need to run a routine in a multitasking system
which "dynamically binds" a structure to a certain value, it will
change its value in someone else's process. THis is very undisciplined
and it is why you can't implement special binding with
(setq *foo* 1)
(defun call-with-foo (new-value thunk)
(let ((old *foo*))
(unwind-protect (progn (setq *foo* new-value) (funcall thunk))
(setq *foo* old))))
This will work in your given function to see a value of *foo* being
new-value, but it will also affect other processes that don't want *foo*
to be other than 1.

CLIM tries to use LETF and loses on hardware platforms. If you've
ever had colormap problems that seem transient and only happen under
multitasking, you're probably a victim. It has a friend called
LETF-GLOBALLY which is what CL is mostly going to implement, but it
has different semantics. LETF-GLOBALLY does just like call-with-foo
above. Sometimes this *is* useful, but you have to distinguish.
For example, I was having a problem recently with resource deadlock
and did something like this (from memory; not tested):

(defvar *my-resource-in-use* nil)
(defun use-my-resource ()
(unwind-protect
(progn (loop (mp:without-preemption
(unless *my-resource-in-use*
(setq *my-resource-in-use* t)
(return)))
(mp:process-wait "Await Resource" '()
#'symbol-value '*my-source-in-use*))
(really-use-my-resource))
(setq *my-resource-in-use* nil)))

Note here that the PURPOSE of the communication was not to inform my
callee, who already knows I've bound this variable--or perhaps doesn't
care. But rather it is to inform the other processes around me. So
in this case, regular unwind-protect is right. But for cases where I
want to call a caller and lie to it about the nature of a data
structure, a LETF-GLOBALLY kind of thing (based on UNWIND-PROTECT)
won't work because other processes will be randomly perturbed.

o, for example, in T we had
(dynamic-wind winder-thunk unwinder-thunk body-thunk)
or some such thing. I don't recall the specific syntax but it was like
that. And with this you could do things like:

(defglobal foo 1) ;Assume we can define globals but not do special binding
;and we want to implement special binding

(let ((cell 2))
(dynamic-wind (lambda () (rotatef foo cell))
(lambda () (rotatef foo cell))
(lambda () ... stuff using foo...)))

It was an interesting observation and it's why I picked this example
that the winder was often the same as the unwinder. Anyone who's ever
looked at shallow binding will know that often it's implemented by
swapping and swapping two cells--one on the stack and one in the heap,
so it's not surprising that a functional model would be similiarly
symmetric.

Well, my power just failed and I was lucky I was able to dial back in and
re-grab the telnet stream to this remote host. I'd better take that as an
omen and send this off. It's past my bedtime anyway... Hopefully there
is enough up there to chew on...
--Kent

David Bakhash

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
Erik Naggum <cle...@naggum.no> writes:

> Antoniotti's LETBIND, I came across many versions of LETF about the same
> time (1994-10). LETF binds places like SETF. although various versions
> of LETF float around, I haven't found one that does multiple values right
> and the version I hacked to handle it doesn't bind new variables, it only
> deals with existing bindings. that may or may not be the right thing, of
> course. is there a "definitive" LETF somewhere?

believe it or not, Dave G. implemented LETF in the cl.el elisp package
for GNU Emacs and XEmacs. Unfortunately, it seems to act funny as
well. in your *scratch* buffer try this:

(setq z (cons 0 1))

(letf (((car z) 3)
((cdr z) 4))
z)

==> (0 . 1)

that's not what's supposed to happen. But if you do this, things look
better:

(letf (((car z) 3)
((cdr z) 4))
(car z))

==> 3

don't ask me to explain; I'm still waiting for the package author to
explain his LETF implementation to me.

dave

Kelly Murray

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
David Bakhash wrote:
> (defun f (x)
> (let* ((y (* x 5))
> (a (nth-value 0 (g y)))
> (b (nth-value 1 (g y)))
> (c (nth-value 2 (g y))))
> (list a b c)))

The let syntax that I use multiple values:

(let y = (* x 5)
(a b c) = (g y)
do
(list a b c)
)

I've found this to simply my code quite dramatically in many cases
in particular eliminates yet another bunch of
uselesslyunneededonlyappreciatedbyobscurites parenthesis.

For example:
(let (sec1 min1) = (decode-universal-time then)
(sec2 min2) = (decode-universal-time now)
do
..)

Here is the definition:

;;
;; (let x = 10
;; y = 20
;; do
;; ...)
;; this doesn't quite handle parallel bindings fully yet using AND
;; (let x = (car x) and
;; y = (cdr x)
;; z = 2
;; do ...)
;; But does handle multiple values, e.g.
;; (let x = 10
;; (q r) = (floor 1 2)
;; do ...)
;;
(defmacro let (&rest args)
(cond
((and (listp (first args)) ;; CL syntax (let (..vars..) ...body)
(or (not (symbolp (second args)))
(not (eqs (second args) '=)))) ;; not (let (x y) = .. )
`(lisp::let ,@args))
(t
(let* ((bindings nil)
(var nil)
(val nil)
(letop 'lisp::let*))
(loop while args
do
(setf var (first args))
(cond
((listp var) ;; multiple values, start another level of let
(cond ((and (eqs (second args) '=)
(cdddr args)) ;; something following it
(return
`(,letop ,(nreverse bindings)
(multiple-value-bind ,var ,(third args)
;; continue with let as inner level expansion
(let ,@(cdddr args))
))))
(t
(error "syntax error in let, missing = or form after ~A vars" var)
)))
((eqs var 'do)
(return
`(,letop ,(nreverse bindings) ,@(cdr args))))
((eqs var 'and)
(setf letop 'lisp::let)
(setf args (cdr args)))
((cdr args)
(cond ((eqs (second args) '=)
(setf val (third args))
(cond ((eqs val 'do)
(error "syntax error in let, missing value for ~A"
var))
(t
(push (list var (third args)) bindings)
(setf args (cdddr args)))))
(t
(error "syntax error in let, missing '=' after ~A"
var))))
(t
(error "syntax error in let, missing '=' before ~A"
(second args))))
)))))

David Bakhash

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
Erik,

I tried `letbind', but had no luck whatsoever. I don't understand
what it's really trying to do:

In Allegro CL:

[8] LETBIND(20):
(letbind ((x 4)
(y 5))
(* x y))
20

so far so good. But now:

[8] LETBIND(21):
(letbind ((x 3)
(y z) (2 3))
(* x y z)) (letbind ((x 3)
(y z) (2 3))
(* x y z))
Error: Malformed binding (2 3) in

(LETBIND ((X 3) (Y Z) (2 3)) (* X Y Z))
[condition type: MALFORMED-BINDING]

so I figured that it was just a matter of putting the destructured
list (i.e. the `(y z) (2 3)') inside another list:

[9] LETBIND(22):
(letbind ((x 3)
((y z) (2 3)))
(* x y z))
[9] LETBIND(22): Error: Malformed binding ((Y Z) (2 3)) in

(LETBIND ((X 3) ((Y Z) (2 3))) (* X Y Z))
[condition type: MALFORMED-BINDING]


Basically, no luck with that. Plus, it can't handle things like
`aref', or `car'/`cdr', like letf, right?

dave

Erik Naggum

unread,
Jul 29, 1998, 3:00:00 AM7/29/98
to
* David Bakhash

| I tried `letbind', but had no luck whatsoever.

it appears from your examples that you haven't read the documentation
that comes with it at all. why not? here's the one for LETBIND:

"Macro usable to combine the features of the bindings constructs of CL.
This macro combines LET, MULTIPLE-VALUE-BIND and DESTRUCTURING-BIND.
The syntax is '(letbind (<binding>*) <forms>)'.
Each bindings has the following syntax
binding ::= symbol form [decl*] |
(symbols*) [:destructuring | :values] form [decls*]."

Jens Kilian

unread,
Jul 30, 1998, 3:00:00 AM7/30/98
to
Kent M Pitman <pit...@world.std.com> writes:
> o, for example, in T we had
> (dynamic-wind winder-thunk unwinder-thunk body-thunk)
> or some such thing. I don't recall the specific syntax but it was like
> that. And with this you could do things like:
>
> (defglobal foo 1) ;Assume we can define globals but not do special binding
> ;and we want to implement special binding
>
> (let ((cell 2))
> (dynamic-wind (lambda () (rotatef foo cell))
> (lambda () (rotatef foo cell))
> (lambda () ... stuff using foo...)))

IIRC, the latest-and-greatest Revised^N Report on Scheme has a similar
construct.

Jens.
--
mailto:j...@acm.org phone:+49-7031-14-7698 (HP TELNET 778-7698)
http://www.bawue.de/~jjk/ fax:+49-7031-14-7351
PGP: 06 04 1C 35 7B DC 1F 26 As the air to a bird, or the sea to a fish,
0x555DA8B5 BB A2 F0 66 77 75 E1 08 so is contempt to the contemptible. [Blake]

Kent M Pitman

unread,
Jul 30, 1998, 3:00:00 AM7/30/98
to
Jens Kilian <Jens_...@bbn.hp.com> writes:

> Kent M Pitman <pit...@world.std.com> writes:

> > o, for example, in T we had
> > (dynamic-wind winder-thunk unwinder-thunk body-thunk)

...


>
> IIRC, the latest-and-greatest Revised^N Report on Scheme has a similar
> construct.

How embarrassing not to have remembered this since I'm among the
"authors" of that report. (And although being in that list doesn't
necessarily mean one reads or contributes to every draft, I did both
read and contribute to this one, so you'd think I'd have remembered.
Oh well.) Yes, this is the same operator and you're right, it did get
introcued between n=4 and n=5, where n=5 is the current report. Thanks
for reminding/correcting me.
--Kent

Rob Warnock

unread,
Jul 30, 1998, 3:00:00 AM7/30/98
to
Jens Kilian <Jens_...@bbn.hp.com> wrote:
+---------------

| Kent M Pitman <pit...@world.std.com> writes:
| > o, for example, in T we had
| > (dynamic-wind winder-thunk unwinder-thunk body-thunk)
| > or some such thing. I don't recall the specific syntax...

|
| IIRC, the latest-and-greatest Revised^N Report on Scheme has a similar
| construct.
+---------------

Except that the arg order is different (though more intuitive to me)
than what Kent showed for T:

(dynamic-wind before-thunk body-thunk after-thunk)


-Rob

p.s. Not too long ago there was a fairly long thread in comp.lang.scheme
on the implications of the interactions of call/cc, dynamic-wind, and
exception handlers. It ain't pretty, and it's not clear what the right
semantics are (or even if there is just one "right" set of semantics).

-----
Rob Warnock, 7L-551 rp...@sgi.com http://reality.sgi.com/rpw3/
Silicon Graphics, Inc. Phone: 650-933-1673
2011 N. Shoreline Blvd. FAX: 650-933-4392
Mountain View, CA 94043 PP-ASEL-IA

Scott L. Burson

unread,
Jul 31, 1998, 3:00:00 AM7/31/98
to
Somebody, now lost in the mists, wrote:
> >>
> >> I have the following problem. I have a function which returns 3
> >> values, and I need them all. Let's suppose that the function (we'll
> >> call it "g") returns the sqrt, square, and cube of its argument. And
> >> so I have this let* statement that wants to look like this:
> >>
> >> (defun f (x)
> >> (let* ((y (* x 5))
> >> (a (nth-value 0 (g y)))
> >> (b (nth-value 1 (g y)))
> >> (c (nth-value 2 (g y))))
> >> (list a b c)))

Here's another approach: a macro NLET that I invented years ago to support a
more functional programming style. It combines the functionality of LET, LET*,
and MULTIPLE-VALUE-BIND.

The above example could be written with NLET as follows:

(defun f (x)
(nlet ((y (* x 5))
((a b c (g y))))
(list a b c)))

Here's how to read this. The binding-spec (A B C (G Y)) specifies that A, B,
and C are to be bound to the (first) three values returned by (G Y). The extra
pair of parens around this binding-spec indicates that the form (G Y) is
evaluated in a scope in which Y has been bound.

More generally: (1) in a binding-spec with N subforms, N >= 2, the first N - 1
subforms are variables to be bound to the values of the expression which is the
last subform. This is a straightforward generalization of the standard LET
binding-spec. (2) All bindings at a given level of nesting are done in
parallel, in a scope that includes all the bindings at a lower (less nested)
level.

Here's another example:

(nlet ((a (f1 x))
((b (f2 a))
(c (f3 a))
((d e (f4 b c))))
(q (g y)))
...)

The bindings of A and Q are made in parallel at the outermost level. Both are
visible to the calls to F2, F3, and F4, although as a stylistic matter I have
written the binding of Q last since it is not actually referred to by those
calls (and also to demonstrate that the syntax permits this). The bindings of B
and C are again made in parallel and are visible to the call to F4. Finally,
the bindings of D and E are visible in the body.

One of the several motivations for NLET came from wanting a functional way to
write something like this:

(let ((a ...)
(b ...))
(when (< a b)
(psetq a b b a))
...)

With NLET, this can be written

(nlet ((a ...)
(b ...)
((a b (if (< a b) (values b a) (values a b)))))
...)

Anyway, here is the code. This implementation, unfortunately, makes no attempt
to do anything useful with declarations.


;;; ================================================================
;;; A variation on the let macro. It is entirely upward compatible with the
;;; standard version; in addition, it cleans up nested lets. Also, it
;;; generates multiple-value-bind forms if more than one variable appears
;;; in a clause (all but the final subform are taken to be variables). An
example:
;;; The following two forms are equivalent:
;;; (let (foo
;;; (bar (car cruft)))
;;; (multiple-value-bind (quux zot)
;;; (decipher bar)
;;; (do-something-with foo bar quux zot)))
;;;
;;; and
;;;
;;; (nlet (foo
;;; (bar (car cruft))
;;; ((quux zot (decipher bar))))
;;; (do-something-with foo bar quux zot)))
;;;
;;; The idea is that every let-parameter at a depth deeper than one in the
;;; parameter list assumes the binding of everything at lesser depth.
;;; Note that there's no guarantee that the value forms will be evaluated in any
;;; particular order; but there is a guarantee that at each level, all the
bindings
;;; are done in parallel.

(defmacro NLET (params &body body)
(car (nlet>expand params body)))

(defun nlet>expand (params body)
(multiple-value-bind (this-level-single this-level-multiple next-level)
(nlet>split-level params nil nil nil)
(nlet>expand-1 this-level-single this-level-multiple next-level body)))
(defun nlet>expand-1 (this-level-single this-level-multiple next-level body)
(cond ((and this-level-multiple
(null (cdr this-level-multiple))
(null this-level-single))
`((multiple-value-bind ,(butlast (car this-level-multiple))
,(car (last (car this-level-multiple)))
. ,(nlet>expand-1 nil nil next-level body))))
(this-level-multiple
(let ((vars (butlast (car this-level-multiple))))
(let ((gensyms (mapcar #'(lambda (ignore) (gensym)) vars)))
`((multiple-value-bind ,gensyms
,(car (last (car this-level-multiple)))
. ,(nlet>expand-1 (nconc (mapcar #'list vars gensyms) this-level-single)
(cdr this-level-multiple) next-level body))))))
(this-level-single
(multiple-value-bind (ignores real-clauses)
(nlet>npartition-list #'(lambda (clause)
(and (listp clause) (eq (car clause) 'ignore)))
this-level-single)
`((progn ,@(mapcar #'cadr ignores)
(let ,real-clauses
. ,(nlet>expand-1 nil nil next-level body))))))
(next-level
(nlet>expand next-level body))
(t body)))

(defun nlet>split-level (params this-level-single this-level-multiple
next-level)
(if (null params)
(values (nreverse this-level-single) (nreverse this-level-multiple)
next-level)
(let ((param (car params)))
(cond ((and (listp param) (listp (car param)))
(nlet>split-level (cdr params) this-level-single this-level-multiple
(append next-level param)))
((and (listp param) (cddr param))
(nlet>split-level (cdr params) this-level-single
(cons param this-level-multiple) next-level))
(t
(nlet>split-level (cdr params) (cons param this-level-single)
this-level-multiple next-level))))))

; DEL-IF and DEL-IF-NOT combined.
(defun nlet>npartition-list (pred list)
"Destructively partitions LIST according to PRED. Returns two values: a list
of the elements of LIST that satisfy PRED, and a list of those that do not
satisfy
PRED. LIST becomes one of those two, depending on the fate of its first
element."
(let ((true-list nil)
(false-list nil))
(do ((true-loc nil)
(false-loc nil)
(list list (cdr list)))
((null list)
(if true-loc (rplacd true-loc nil))
(if false-loc (rplacd false-loc nil))
(values true-list false-list))
(cond ((funcall pred (car list))
(if true-loc (rplacd true-loc list)
(setq true-list list))
(setq true-loc list))
(t
(if false-loc (rplacd false-loc list)
(setq false-list list))
(setq false-loc list))))))

; A utility to return the list of variables bound by some NLET-clauses.
(defun nlet>bound-vars (clauses)
"Given a list of NLET-clauses, returns a list of the variables bound by the
clauses."
(and clauses
(nconc (let ((clause (car clauses)))
(cond ((symbolp clause) (and (not (eq clause 'ignore)) (cons clause nil)))
((symbolp (car clause)) (remove 'ignore (butlast clause)))
(t (nlet>bound-vars clause))))
(nlet>bound-vars (cdr clauses)))))

;;; ================================================================

-- Scott

* * * * *

To use the email address, remove all occurrences of the letter "q".

0 new messages