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

Macros OR, AND

11 views
Skip to first unread message

Vladimir Zolotykh

unread,
Nov 16, 2001, 4:17:24 AM11/16/01
to
html-drafts-3(432): (or nil (values 1 2))
1
2
html-drafts-3(433): (or (values 1 2) nil)
1
html-drafts-3(434):

This way macros OR and AND works.
Corresponding macros would be simpler in that case I think.
Are there other reason (style/readability/etc.) for such behavior ?

--
Vladimir Zolotykh gsm...@eurocom.od.ua

Thomas F. Burdick

unread,
Nov 16, 2001, 4:34:20 AM11/16/01
to
Vladimir Zolotykh <gsm...@eurocom.od.ua> writes:

> html-drafts-3(432): (or nil (values 1 2))
> 1
> 2
> html-drafts-3(433): (or (values 1 2) nil)
> 1
> html-drafts-3(434):
>
> This way macros OR and AND works.
> Corresponding macros would be simpler in that case I think.
> Are there other reason (style/readability/etc.) for such behavior ?

My guess would be that the reason is historical (not breaking old code
that depended on that behavior), and efficiency. As for the latter,
can you think of any way to implement an efficient multiple-value OR?
The only way I can think of is:

(defmacro mv-or (&rest forms)
(cond
((null forms) ())
((null (cdr forms)) (car forms))
(t (let ((sym (gensym)))
`(let ((,sym (multiple-value-list ,(car forms))))
(if (car ,sym)
(values-list ,sym)
(or ,@(cdr forms))))))))

Although maybe it would be better for OR and AND to only return the
primary value, even for the last form.

--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'

Michael Hudson

unread,
Nov 16, 2001, 4:57:48 AM11/16/01
to
Vladimir Zolotykh <gsm...@eurocom.od.ua> writes:

> html-drafts-3(432): (or nil (values 1 2))
> 1
> 2
> html-drafts-3(433): (or (values 1 2) nil)
> 1
> html-drafts-3(434):
>
> This way macros OR and AND works.
> Corresponding macros would be simpler in that case I think.
> Are there other reason (style/readability/etc.) for such behavior ?

Well, can you implement them so they work the other way? When
evaluating (or a b) you have to store the result of evaluating a
somewhere while checking b, and I don't think you can do that for a
complete set of values (well, without materialising a list, which
would kind of defeat the point).

I'd be interested in being wrong, though.

Cheers,
M.

--
A.D. 1517: Martin Luther nails his 95 Theses to the church door and
is promptly moderated down to (-1, Flamebait).
-- http://slashdot.org/comments.pl?sid=01/02/09/1815221&cid=52
(although I've seen it before)

Kent M Pitman

unread,
Nov 16, 2001, 8:22:00 AM11/16/01
to
Vladimir Zolotykh <gsm...@eurocom.od.ua> writes:

> html-drafts-3(432): (or nil (values 1 2))
> 1
> 2
> html-drafts-3(433): (or (values 1 2) nil)
> 1
> html-drafts-3(434):
>
> This way macros OR and AND works.
> Corresponding macros would be simpler in that case I think.
> Are there other reason (style/readability/etc.) for such behavior ?

There IS an efficiency concern here, but that concern is not simply
that you can't do multiple-value operations that capture the
appropriate values. A lot of it has to do with the fact that users
often write (or (f) (g)) and you don't know if (f) is being called for
multiple values because it isn't explicit. You'd have to set up a
multiple value call block "just in case", even though it might be
common for predicates to return multiple values and it might also be
utterly irrelevant to the programmer. Yes, people might do (or
(gethash x y) 4) but usually they are not wanting the second value of
gethash, for example, because it's about defaulting, and so is the OR.
So mostly people are just as glad to have the value masked out. The
fact is that while people occasionally remark on this, almost no one
considers it an impediment. It's easy to write the multiple-value
thing as a macro, but almost no one ever does. S

But efficiency aside, there are some philosophical/linguistic issues
for not having multiple values passed through. OR is falseness vs
trueness, and this ends up creating an odd situation where NIL, 3
might be considered false. But is it? 3 is not false. Is NIL, NIL
[as a conceptual pair] false? It [if it can be called that] certainly
is "not NIL" (the definition of truth).

There is also the related philosophical question where some people say
that NIL, NIL should be NIL but that NIL, T should be T because it is
not "uniformly null". This is still another rat's nest I would not like
to get into. [File it under ``why doesn't (member 'a '((a b) c)) return
(A B) since A is "in there somewhere and should be found"?'']

[Complete aside (not about programming per se, but while I'm talking
about related problems, this one is filed in the same hash bucket in
my brain as the above sets of problems): Anyone out there do "challenge
square dancing"? I used to, and used to have similar emotional troubles
with "as couples" genders. The modifier call called "as couples"
causes each pair of two people standing side-by-side in the square to
act as a rigid unit of one in doing a call (which then must not take
more than 4 people rather than the usual 8 since there are only 4 "as
couples" people in a square. I didn't mind this, but it bugged me
when people would give a direction normally given to a (gendered)
person to an as-couples unit. This is only ever done to a unit of
both men or of both women, in practice, but the possibility exists to
do it to a unit containing a man and a woman working as a unit, and
that puts the lie to the claim that an "as couples" person has a
gender.]

Programmers with a local need are quick to insist they would "ignore"
this or that problem in exchange for cool extra functionality. But
different programmers are willing to ignore different things, and
collectively as a community, people expect a lot more of a language
than they do of a toolbox. As a random tool that someone is passing
around, any of these definitions of OR would suffice, and indeed the
HyperMeta toolbox has a MULTIPLE-VALUE-OR that acts on the first value
and carries along the second value as a rider. I'm comfortable with
that as a tool. And maybe it would gain such widespread acceptance it
would grow up to be standard. But doing it through usage testing and
not out of sheer theory seems like a good idea to me in this case,
since there are multiple major design points (the ones I noted above,
and maybe others) that you want to make sure you get right. And, as
with the IF* discussion [just mentioning it, not wanting to re-open
it, thank you], the question of promoting something from a private
something with a happy set of voluntary users to a public part of our
shared community that every user confronts can end up confronting
controversy if not done carefully.


Thomas F. Burdick

unread,
Nov 16, 2001, 4:13:40 PM11/16/01
to
Kent M Pitman <pit...@world.std.com> writes:

> Vladimir Zolotykh <gsm...@eurocom.od.ua> writes:
>
> > html-drafts-3(432): (or nil (values 1 2))
> > 1
> > 2
> > html-drafts-3(433): (or (values 1 2) nil)
> > 1
> > html-drafts-3(434):
> >
> > This way macros OR and AND works.
> > Corresponding macros would be simpler in that case I think.
> > Are there other reason (style/readability/etc.) for such behavior ?

[...]

> There is also the related philosophical question where some people say
> that NIL, NIL should be NIL but that NIL, T should be T because it is
> not "uniformly null". This is still another rat's nest I would not like
> to get into. [File it under ``why doesn't (member 'a '((a b) c)) return
> (A B) since A is "in there somewhere and should be found"?'']

I think it's interesting that everyone's (including my) first reaction
to this was to assume that the poster wanted a multiple-value OR. The
question could equally be asking why multiple values are allowed to
drop through in the last case. That would actually seem to make a lot
more sense, it seems. If OR concerns itself with trueness, what
*should* one make of NIL, NIL (or NIL, T) coming out the other end?

Barry Margolin

unread,
Nov 16, 2001, 4:45:23 PM11/16/01
to
In article <xcvofm2...@apocalypse.OCF.Berkeley.EDU>,

Thomas F. Burdick <t...@apocalypse.OCF.Berkeley.EDU> wrote:
>I think it's interesting that everyone's (including my) first reaction
>to this was to assume that the poster wanted a multiple-value OR. The
>question could equally be asking why multiple values are allowed to
>drop through in the last case. That would actually seem to make a lot
>more sense, it seems. If OR concerns itself with trueness, what
>*should* one make of NIL, NIL (or NIL, T) coming out the other end?

This is a remnant of an old Lisp idiom. Before WHEN and UNLESS were
introduced (and even since then, as old habits die hard), it was common to
use AND and OR for these purposes, e.g.

(and (not (zerop x)) (/ y x)) == (unless (zerop x) (/ y x))

Typically in code like this, everything up to the last form is intended
just for its truth value, while the last form is still used for its full
value. When multiple values were added, it made sense to retain them in
that place. There shouldn't be any overhead in this case, because the last
form doesn't need its value to be saved and tested, it just gets returned.

--
Barry Margolin, bar...@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.

Daniel Barlow

unread,
Nov 16, 2001, 6:39:15 PM11/16/01
to
Barry Margolin <bar...@genuity.net> writes:

> This is a remnant of an old Lisp idiom. Before WHEN and UNLESS were
> introduced (and even since then, as old habits die hard), it was common to
> use AND and OR for these purposes, e.g.
>
> (and (not (zerop x)) (/ y x)) == (unless (zerop x) (/ y x))

This is an old idiom? I thought it was a current idiom (and no, I'm
not an old programmer). Especially if you have more than two forms,

(and (eql op #\/)


(not (zerop x))
(/ y x))

would just look ugly expressed as nested conditionals. I liken it to
a shell script that starts with "set -e" : keep evaluating the
contents until something "fails"


-dan

--

http://ww.telent.net/cliki/ - Link farm for free CL-on-Unix resources

Thomas F. Burdick

unread,
Nov 16, 2001, 11:09:19 PM11/16/01
to
Daniel Barlow <d...@telent.net> writes:

> Barry Margolin <bar...@genuity.net> writes:
>
> > This is a remnant of an old Lisp idiom. Before WHEN and UNLESS were
> > introduced (and even since then, as old habits die hard), it was common to
> > use AND and OR for these purposes, e.g.
> >
> > (and (not (zerop x)) (/ y x)) == (unless (zerop x) (/ y x))
>
> This is an old idiom? I thought it was a current idiom (and no, I'm
> not an old programmer). Especially if you have more than two forms,
>
> (and (eql op #\/)
> (not (zerop x))
> (/ y x))
>
> would just look ugly expressed as nested conditionals. I liken it to
> a shell script that starts with "set -e" : keep evaluating the
> contents until something "fails"

I can see doing this for AND, but I think it's a bad idea, because it
makes no sense for OR. With AND, the value of the form is only ever
going to be the value(s) of the last form, or NIL. With OR, though,
it's weird.

Erik Naggum

unread,
Nov 16, 2001, 11:49:16 PM11/16/01
to
* Thomas F. Burdick

| I can see doing this for AND, but I think it's a bad idea, because it
| makes no sense for OR. With AND, the value of the form is only ever
| going to be the value(s) of the last form, or NIL. With OR, though,
| it's weird.

(or (cached-result mumble frotz)
(cheap-algorithm-if-possible mumble frotz)
(expensive-general-case mumble frotz))

I also tend to write

(or (gethash mumble frotz)
(setf (gethash mumble frotz) (whatever mumble)))

mostly because I cannot agree with myself on what the name of a macro
that does this should be called. The non-zero cost of computing the
proper entry in the hash table should have been possible to optimize.
Since the value to be stored could be expensive, too, it would be a
pretty complex function interface:

(gethash-but-sethash-if-unset <key> <hashtable> <function>)

typically called with a function (or probably a closure) to call to
obtain the value to store if the hash table had no entry for that key.
It is too complicated and messy for whatever limited utility it has.

Adding more stuff to the language, one could of course have a new macro
to build functions that should be called in such functions:

(gethash-but-sethash-if-unset <key> <hashtable> (delay <expression>))

where delay would be defined no more complicated than this:

(defmacro delay (form)
`(lambda () ,form))

but it would communicate the programmer's intent better than the lambda
expression alone would do. However, this is somethiing that needs a bit
more "community support" and wider application and implementation if it
were to be useful. Languages that have such a delay form typically have
a way to call the function the first time the value is asked for, storing
ib back into the variable. And that is the exact feature sought here.

For the time being, I think it looks better to write

(or end (setq end (length <sequence>)))

than a much more complicated mechanism.

///
--
Norway is now run by a priest from the fundamentalist Christian People's
Party, the fifth largest party representing one eighth of the electorate.
--
Carrying a Swiss Army pocket knife in Oslo, Norway, is a criminal offense.

Thomas F. Burdick

unread,
Nov 17, 2001, 6:36:42 PM11/17/01
to
Erik Naggum <er...@naggum.net> writes:

> * Thomas F. Burdick
> | I can see doing this for AND, but I think it's a bad idea, because it
> | makes no sense for OR. With AND, the value of the form is only ever
> | going to be the value(s) of the last form, or NIL. With OR, though,
> | it's weird.
>
> (or (cached-result mumble frotz)
> (cheap-algorithm-if-possible mumble frotz)
> (expensive-general-case mumble frotz))

But this is exactly the weird case! expensive-general-case can return
multiple values, but cached-result can't! With the AND analogue,
you're fine because the last form determines the results. Lets say
this computation is seeing whether something is computable, so its
primary value is a boolean. Cool, OR is appropriate here. But its
second value is the result of the computation, in case you want it
(and assuming there is a result). Although CACHED-RESULT might return
the second value, the whole OR form can only give it to you in the
EXPENSIVE-GENERAL-CASE. But it *does* give it to you there. You
don't end out with this problem with AND, which is why I think the OR
version is weird. Admittedly, it's not much of a problem, but I do
find the specialness of the last form annoying.

> I also tend to write
>
> (or (gethash mumble frotz)
> (setf (gethash mumble frotz) (whatever mumble)))
>
> mostly because I cannot agree with myself on what the name of a macro
> that does this should be called. The non-zero cost of computing the
> proper entry in the hash table should have been possible to optimize.

Actually, I find this a funny example because it's not quite
appropriate for OR (at least in the general case). I've got a macro:

(defmacro sethash-maybe (key hash-table value-form)
(with-gensyms (val val?)
`(multiple-value-bind (,val ,val?) (gethash ,key ,hash-table)
(if ,val? ,val (setf (gethash ,key ,hash-table) ,value-form)))))

It can't use OR because that might be fine for some cases, where I
know that NIL isn't a valid value, but it can be, sometimes. (I can
understand not being able to name that macro, though. I started
naming things ...-maybe that would have had super long names, and I
didn't like it at first, but I got used to it. It's probably a bad
name, but it's fine for my personal use :) )

> Since the value to be stored could be expensive, too, it would be a
> pretty complex function interface:
>
> (gethash-but-sethash-if-unset <key> <hashtable> <function>)
>
> typically called with a function (or probably a closure) to call to
> obtain the value to store if the hash table had no entry for that key.
> It is too complicated and messy for whatever limited utility it has.
>
> Adding more stuff to the language, one could of course have a new macro
> to build functions that should be called in such functions:
>
> (gethash-but-sethash-if-unset <key> <hashtable> (delay <expression>))
>
> where delay would be defined no more complicated than this:
>
> (defmacro delay (form)
> `(lambda () ,form))
>
> but it would communicate the programmer's intent better than the lambda
> expression alone would do. However, this is somethiing that needs a bit
> more "community support" and wider application and implementation if it
> were to be useful. Languages that have such a delay form typically have
> a way to call the function the first time the value is asked for, storing

> [it] back into the variable. And that is the exact feature sought here.

This is a case where I wonder if the flexibility of macros hasn't
maybe prevented a more powerful notation from having developed,
because we can fake it every specific time we need it. Oh well.

Erik Naggum

unread,
Nov 17, 2001, 10:07:11 PM11/17/01
to
* Thomas F. Burdick

| But this is exactly the weird case! expensive-general-case can return
| multiple values, but cached-result can't!

Oh, but I used my own definition of the macro or that solves this
problem, and you SHOULD NOT use the braindamaged standard macro. :)

But seriously, I recognize your problem. It might actually be a good
idea to specify or and cond to be multivalued -- it would not take a huge
effort to provide compiler support for this, but there is a little more
effort involved when interpreted. However, one of the problems with
multivalued functions is that the caller context knows whether it will
return single or multiple values, but nobody is listening. That is, if a
function may be multivalued, it should be able to skimp on the cost of
preparing for returning multiple values if only the primary value is
used. This might, understandably, have created a reluctance to use
multiple values by default.

The "problem" of communicating the expectation of multiple values has
some similarities with the annoying tail-call problem, but fortunately, a
compiler or interpreter will have to know how to set up its multivalue
return mechanism or pass through whatever the value-returning form of the
function returned, so the point where this information would both be
passed _and_ understood is already known. However, there is no language
support for this knowledge, so there is no way to utilize that knowledge.
E.g., cond would need to know whether the "calling" form was about to
return its value or not to make this painless and transparent insead of
costly and cumbersome.

The typical implementation of the macro or is to use the feature of the
macro cond that it return the value of the test-form if there are no body
forms, but it is specified to return only the primary value in that case.
To amend this would require some work. If a cond form transform into an
if form like this:

(cond (test-form) ...)
-> (let ((<gensym> test-form))
(if <gensym>
<gensym>
(cond ...)))

It would have to be something like this

-> (let ((<gensym> (multiple-value-list test-form)))
(if (first <gensym>)
<gensym>
(cond ...)))

except that the internal multivalue return mechanism would probably be
used instead of going through the expensive list representation, such
that it would not actually need to allocate anything in the caller. (I
am concerned with this only because the penalty for using multiple values
should be so low that people would not have to think twice about it.)

In order for this to _really_ work well, a new language feature would be
very welcome. Implementations already have to have _some_ means to
return multiple values, so suppose that multiple values were represented
by a first-class object instead of the sort of "transient" magic it is
today. The function values would create and return such an object. If
stored in a variable and later referenced, its value would be the primary
value. If referenced by the multivalue special operators, it would work
just like it were the original call to values as we are used to it today
in the form of storing the value of multiple-value-list and expanding it
back to multiple values with (apply #'values <list>).

The complicating situation here is that this new kind of first-class
object needs to work transparently as if it were its primary value.
(Back on the good old PDP-10 this kind of data-driven indirection would
not have been a problem, but sadly, modern processors are so deficient.
Then again, many things would not have been problems if the PDP-10 had
been the model for modern processors. You who knew the PDP-10, know what
I mean. The rest should know that this not mere nostalgia.)

Note that this would make the original expansion of cond continue to work
with multiple values. It would also mean we would have an asymmetry that
some might find quite disconcerting:

(let* ((foo (values 1 2 3))
(bar foo))
(list (eq foo bar) (multiple-value-list foo) (multiple-value-list bar)))
=> (t (1 2 3) (1))

| (I can understand not being able to name that macro, though. I started
| naming things ...-maybe that would have had super long names, and I
| didn't like it at first, but I got used to it. It's probably a bad name,
| but it's fine for my personal use :) )

Perhas something along the line of intern?

| This is a case where I wonder if the flexibility of macros hasn't maybe
| prevented a more powerful notation from having developed, because we can
| fake it every specific time we need it. Oh well.

Well, we have symbol-macros, which can be really powerful, too, once
people figure out how to use them. Making the full use of existing
features is quite hard, so it may be that implementing something on top
of macros simply does not yield enough of an inconvenience to spawn
serious interest in alternatives? That is, not the flexibility of
macros, but the lack of inconvenience in doing something with them.

Frode Vatvedt Fjeld

unread,
Nov 18, 2001, 10:14:07 AM11/18/01
to
Erik Naggum <er...@naggum.net> writes:

> Implementations already have to have _some_ means to return multiple
> values, so suppose that multiple values were represented by a
> first-class object instead of the sort of "transient" magic it is
> today. The function values would create and return such an object.

This seems to me a very bad idea, as the transient magic mode of
operation enables a "transient-extent" (efficient) kind of allocation
of the values. If I understand you correctly, those multiple-value
objects would need to be consed at every function return, almost like
python's bad excuse for multiple values.

I (also) find it slightly strange that cond must coerce test-form
returns to single-values. Seeing as cond is defined to be a macro, I
suppose that is the reason; a cond written in terms of if and let
would need to use m-v-list to support multiple-values. However, if
cond was a special operator, I believe implementing a multiple-values
returning cond would be non-problematic for most implementations. I
have implemented a cond special operator, and in fact the requirement
to return a single value from test-forms is just a nuisance, it would
be easier to return all the test-forms values.

But then again there may have been (other) good reasons why cond was
selected to be a macro and if a special operator, I don't know (but
I'd like to..)

--
Frode Vatvedt Fjeld

Thomas F. Burdick

unread,
Nov 18, 2001, 3:55:32 PM11/18/01
to
Erik Naggum <er...@naggum.net> writes:

> In order for this to _really_ work well, a new language feature would be
> very welcome. Implementations already have to have _some_ means to
> return multiple values, so suppose that multiple values were represented
> by a first-class object instead of the sort of "transient" magic it is
> today. The function values would create and return such an object. If
> stored in a variable and later referenced, its value would be the primary
> value. If referenced by the multivalue special operators, it would work
> just like it were the original call to values as we are used to it today
> in the form of storing the value of multiple-value-list and expanding it
> back to multiple values with (apply #'values <list>).
>
> The complicating situation here is that this new kind of first-class
> object needs to work transparently as if it were its primary value.

Actually, I've had this on the mind recently, as I've been
implementing a Lisp->C++ compiler. All Lisp functions (actually, all
Lisp functions whose types aren't declared) return an object of type
multiple_values_t that can be cast to a lisp_obj_t if the caller just
cares about the first value. multiple_values_t is something like:

typedef struct _multiple_values_s
{
lisp_obj_t primary_value;
int number_of_values;
} multiple_values_t; // ick, the other values follow.
// Use pointer artithmetic.

The idea is to provide a fairly conveniant interface for the consuming
C++ code. So, I've got these objects that represent multiple values.
It seems silly that the C++ code should be favored here, so I want to
expose this object to Lisp code. Besides, if I don't, I know myself,
and I'll just use inlined C++ to get to it anyway. So I've added a
special form multiple-value-object, that returns a multiple-values
object for whatever values it receives. Plus a couple functions,
number-of-values and values-object:

(multiple-value-object (values :foo :bar :baz))
=> #<Multiple Values 3 :FOO :BAR :BAZ>
(number-of-values *)
=> 3
(values-object **)
=> :FOO;
:BAR;
:BAZ

I'm not thrilled with the names, but I am thrilled with the concept.
It's been making my life as a macro-writer a lot easier.
Unfortunately, this is an aspect of this dialect that can't be
implemented on top of ANSI CL, which doesn't make me so happy
(although I guess it could be emulated with multiple-value-list, but
that would go against its entire raison d'ĂȘtre).

One especially good aspect of this approach is that it avoids things
like:

> Note that this would make the original expansion of cond continue to work
> with multiple values. It would also mean we would have an asymmetry that
> some might find quite disconcerting:
>
> (let* ((foo (values 1 2 3))
> (bar foo))
> (list (eq foo bar) (multiple-value-list foo) (multiple-value-list bar)))
> => (t (1 2 3) (1))

Plus in ANSI CL, only a couple special forms such as
multiple-value-call can actually recieve multiple values, and this
retains that property. I haven't actually implemented this, but I'm
planning on generating single-value versions of functions that return
multiple values, for use in cases when the caller will only get one
value. I might be able to use this to prevent the secondary,
tertiary, etc., values from being computed. (I can't think of a way
to do this, though, since in a form like
(values 1 (setf *global-var* 2) 3), I can't very well eliminate the
computation of the second value. Maybe I can have a lazy-values
declaration or something to enable this optimization.)

> | (I can understand not being able to name that macro, though. I started
> | naming things ...-maybe that would have had super long names, and I
> | didn't like it at first, but I got used to it. It's probably a bad name,
> | but it's fine for my personal use :) )
>
> Perhas something along the line of intern?

Hmm, maybe. sethash-maybe is just a special version of my setf-maybe
macro (it has to be special, because it doesn't discriminate on the
primary value). interning-setf? setf-intern? intern-setf? Hrm :-/

> | This is a case where I wonder if the flexibility of macros hasn't maybe
> | prevented a more powerful notation from having developed, because we can
> | fake it every specific time we need it. Oh well.
>
> Well, we have symbol-macros, which can be really powerful, too, once
> people figure out how to use them. Making the full use of existing
> features is quite hard, so it may be that implementing something on top
> of macros simply does not yield enough of an inconvenience to spawn
> serious interest in alternatives? That is, not the flexibility of
> macros, but the lack of inconvenience in doing something with them.

That's kind of what I meant. Macros *can* do impressive stuff, but
it's also pretty easy. About 5 minutes' work can produce some really
cool stuff. A few hours' work can produce amazing systems (both good
and bad).

Bruce Hoult

unread,
Nov 18, 2001, 4:28:39 PM11/18/01
to
In article <xcv7ksn...@apocalypse.OCF.Berkeley.EDU>,
t...@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) wrote:

> The idea is to provide a fairly conveniant interface for the consuming
> C++ code. So, I've got these objects that represent multiple values.
> It seems silly that the C++ code should be favored here, so I want to
> expose this object to Lisp code. Besides, if I don't, I know myself,
> and I'll just use inlined C++ to get to it anyway. So I've added a
> special form multiple-value-object

Dylan has pretty pervasive multiple-values, Certainly, you can have
multiple-values as the result of a block or if or loop just as easily as
you can have them as the result of a function.

Multiple values are passed by some undefined mechanism that is
(hopefully) more efficient than consing up a list or vector, but if you
really want to get at them you can:

define method sincostan(x, y)
let h = sqrt(x * x + y * y);
values(y / h, x / h, y / x)
end;

let (#rest foo) = sincostan(4, 3);
foo;

=> #[0.6, 0.8, 0.75]


This is, as you say, very handy in macros, and the "collect" macro I
showed here the other day uses it.

-- Bruce

Thomas F. Burdick

unread,
Nov 18, 2001, 5:29:52 PM11/18/01
to
Bruce Hoult <br...@hoult.org> writes:

I don't know Dylan, so maybe I'm reading something wrongly, but this
doesn't look like what I'm talking about. Multiple values in CL are
passed by whatever mechanism the implementation can think of, and can
only be recieved by a few forms, like multiple-value-call, and so on.
If you want to get ahold of the values as an object, you can, with
multiple-value-list. That's what it looks like your dylan above is
doing. In Dylan, could you do the following?

let (s) = sincostan(4, 3);
let (s c) = sincostan(4, 3);
let (s c t) = sincostan(4, 3);
let (#rest all) = sincostan(4, 3);

If so, it looks like it's doing the Dylan version of
multiple-value-list, and stuffing them into a vector.

What I was talking about was trying to get a handle on the
implementation's representation of multiple values. This object is
pretty opaque, and you can pretty much only create it from a place
that recieves multiple values, and use it to create another place that
generates those same values for consumption. You can't get at the
values. It's painless in my implementation to get the number of
values, but if I were proposing this to be a part of the language[*],
I would only include m-v-object and values-object. An example of its
usefulness would be:

(defmacro or (&rest forms)
"No, I do not have this definition in my Lisp system :-)"
(cond
((null forms) ())
((null (rest forms)) (first forms))
(t (let ((-vals (gensym)))
`(let ((,-vals (multiple-value-object ,(first forms))))
;; the object itself can be kept on the stack or in
;; registers, or wherever it would normally go
(declare (dynamic-extent ,-vals))
(if (values-object ,-vals)
(values-object ,-vals)
(or ,@(rest forms))))))))

(or (values 1 2 3) nil)
=> 1;
2;
3
(or nil (values 1 2 3))
=> 1;
2;
3

I *could* do this with multiple-value-list and values-list, but that
would cons up a list. If my implementation can avoid consing for
(values 1 2 3), then my OR should probably not cons. That is,
multiple-value-object gives me the ability to write multiple-value
consuming macros that have the efficiency that a special operator
would have. I assume this is why PROG1 is a macro, but
MULTIPLE-VALUE-PROG1 is a special operator.


[*] And if/when the language goes through another round of
standardization, I will, assuming I don't find some pitfalls while
implementing it. If all goes well, I definitely plan to submit
patches to CMUCL, SBCL, and CLISP to do this.

Erik Naggum

unread,
Nov 18, 2001, 8:59:43 PM11/18/01
to
* Frode Vatvedt Fjeld

| This seems to me a very bad idea, as the transient magic mode of
| operation enables a "transient-extent" (efficient) kind of allocation of
| the values. If I understand you correctly, those multiple-value objects
| would need to be consed at every function return, almost like python's
| bad excuse for multiple values.

I have no idea whether you understood me correctly or not, because I
cannot figure out what you are talking about. I think you may have
missed an "instead of" or something.

| I (also) find it slightly strange that cond must coerce test-form returns
| to single-values.

Huh? This is very strange language. It really helps to understand you
if you used the common language of the standard. (That is another good
reason for a community standard as its focal point of agreement.) It is
specified to test and return the primary value.

| Seeing as cond is defined to be a macro, I suppose that is the reason; a
| cond written in terms of if and let would need to use m-v-list to support
| multiple-values. However, if cond was a special operator, I believe
| implementing a multiple-values returning cond would be non-problematic
| for most implementations. I have implemented a cond special operator,
| and in fact the requirement to return a single value from test-forms is
| just a nuisance, it would be easier to return all the test-forms values.

Whether you implement it as a macro or a special operator does not affect
the internal implementation. A symbol in the common-lisp package defined
to hold a amacro does not have to be expanded by the compiler if it can
do smarter things directly, it only needs to be defined as a macro to
make user-defined code walkers see only a known and predefined set of
special operators. You could clearly define a cond that held onto the
multivalue return vector, if that is how they were implemented, and set
up to return it if there were no body-forms.

| But then again there may have been (other) good reasons why cond was
| selected to be a macro and if a special operator, I don't know (but I'd
| like to..)

It was probably selected as a macro because it easier to code-walk if
than cond. The implementation is free to do whatever it pleases as long
as the semantics of the form is maintained.

Bruce Hoult

unread,
Nov 18, 2001, 9:16:48 PM11/18/01
to
In article <xcvu1vr...@apocalypse.OCF.Berkeley.EDU>,
t...@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) wrote:

> > Multiple values are passed by some undefined mechanism that is
> > (hopefully) more efficient than consing up a list or vector, but if you
> > really want to get at them you can:
> >
> > define method sincostan(x, y)
> > let h = sqrt(x * x + y * y);
> > values(y / h, x / h, y / x)
> > end;
> >
> > let (#rest foo) = sincostan(4, 3);
> > foo;
> >
> > => #[0.6, 0.8, 0.75]
> >
> > This is, as you say, very handy in macros, and the "collect" macro I
> > showed here the other day uses it.
>
> I don't know Dylan, so maybe I'm reading something wrongly, but this
> doesn't look like what I'm talking about. Multiple values in CL are
> passed by whatever mechanism the implementation can think of, and can
> only be recieved by a few forms, like multiple-value-call, and so on.

Right.


> If you want to get ahold of the values as an object, you can, with
> multiple-value-list. That's what it looks like your dylan above is
> doing. In Dylan, could you do the following?
>
> let (s) = sincostan(4, 3);
> let (s c) = sincostan(4, 3);
> let (s c t) = sincostan(4, 3);
> let (#rest all) = sincostan(4, 3);

Yes, if you added a couple of commas. You could also do:

let (s, c, #rest all) = sincostan(4.0, 3.0);


That's an oops in my original code, btw ... not much point using integer
division in working with trig :-)


> If so, it looks like it's doing the Dylan version of
> multiple-value-list, and stuffing them into a vector.

There is only a vector involved if you use #rest, otherwise you have no
idea what is involved -- it's implementation-defined.


> What I was talking about was trying to get a handle on the
> implementation's representation of multiple values. This object is
> pretty opaque, and you can pretty much only create it from a place
> that recieves multiple values, and use it to create another place that
> generates those same values for consumption.

Right, but there isn't necessarily "an object" to grab hold of.

As we know, "let" is -- in Common Lisp, Dylan, and Scheme -- exactly
equivilent to a lambda.

(let ((a 3) (b 7)) (+ a b))

by definition means the same as

(funcall (lambda (a b) (+ a b)) 3 7) ;; CL

((lambda (a b) (+ a b)) ;; Scheme

method(a, b) a + b end (3, 7) ;; Dylan (and, yes, this usage
;; looks ugly to me too)

So, the semantics of returning multiple values are exactly the same as
if you wrote sintancos() not as...

define method sincostan(x, y)
let h = sqrt(x * x + y * y);
values(y / h, x / h, y / x)
end;

... but as ...

define method sincostan(x, y, cont)


let h = sqrt(x * x + y * y);

cont(y / h, x / h, y / x)
end;

... and then used it as ...

sintancos(3.0, 4.0,
method(s, c, t)
// s, c and t bound in here
end
);

... or ...

sintancos(3.0, 4.0,
method(s, #rest args)
// s and args vector bound in here
end
);


The multiple values ARE NOT in some hidden object that you might
possibly be able to get a handle on. They are in a good compiled
implementation passed in exactly the same way as any function arguments
are -- in registers, or on the stack or whatever.


> An example of its usefulness would be:
>
> (defmacro or (&rest forms)
> "No, I do not have this definition in my Lisp system :-)"
> (cond
> ((null forms) ())
> ((null (rest forms)) (first forms))
> (t (let ((-vals (gensym)))
> `(let ((,-vals (multiple-value-object ,(first forms))))
> ;; the object itself can be kept on the stack or in
> ;; registers, or wherever it would normally go
> (declare (dynamic-extent ,-vals))
> (if (values-object ,-vals)
> (values-object ,-vals)
> (or ,@(rest forms))))))))
>
> (or (values 1 2 3) nil)
> => 1;
> 2;
> 3
> (or nil (values 1 2 3))
> => 1;
> 2;
> 3

Here's a Dylan implemenation, and the Gwydion "d2c" C code emitted for
the function "foo". I'm not quite sure of your intended semantics for
the OR, so I've choosen to test the first value returned to decide
whether to return. This could be trivially changed to whatever else you
wanted:

--------------------------------------------------
module: mvor

define macro or
{ or(?forms) } => { ?forms }
forms:
{ } => { #f }
{ ?:expression } => { ?expression }
{ ?:expression, ... }
=> {let (result, #rest more-results) = ?expression;
if (result)
apply(values, result, more-results)
else
...
end }
end macro;

define function foo()
let (a, b, c, d) = or(#f, values(1, 2, 3), #f);
report(a, b, c, d);
end foo;


define function report(a, b, c, d)
format-out("a = %=\n", a);
format-out("b = %=\n", b);
format-out("c = %=\n", c);
format-out("d = %=\n", d);
end;

foo();
--------------------------------------------------
bruce@k7:~/programs/dylan/mvor > ./mvor
a = 1
b = 2
c = 3
d = #f
--------------------------------------------------
descriptor_t * mvorZmvorZfoo_FUN(descriptor_t *orig_sp)
{
descriptor_t *cluster_0_top;
descriptor_t L_temp;
descriptor_t L_temp_2;
descriptor_t L_temp_3;

L_temp.heapptr = mvorZliteral.heapptr;
L_temp.dataword.l = 1;
L_temp_2.heapptr = mvorZliteral.heapptr;
L_temp_2.dataword.l = 2;
L_temp_3.heapptr = mvorZliteral.heapptr;
L_temp_3.dataword.l = 3;
cluster_0_top = mvorZmvorZreport_FUN(
orig_sp, L_temp, L_temp_2, L_temp_3, dylanZfalse);
return cluster_0_top;
}
--------------------------------------------------

This code is identical to what would be generated if I had simply
written "report(1, 2, 3, #f)".


> I *could* do this with multiple-value-list and values-list, but that
> would cons up a list. If my implementation can avoid consing for
> (values 1 2 3), then my OR should probably not cons. That is,
> multiple-value-object gives me the ability to write multiple-value
> consuming macros that have the efficiency that a special operator
> would have.

As you can see, there is no consing in the Dylan, despite my use of
#rest.

-- Bruce

Frode Vatvedt Fjeld

unread,
Nov 19, 2001, 4:18:06 AM11/19/01
to
Erik Naggum <er...@naggum.net> writes:

> It was probably selected as a macro because it easier to code-walk
> if than cond. The implementation is free to do whatever it pleases
> as long as the semantics of the form is maintained.

Yes but the point I tried to make (in not so good language) was that
since cond is a macro, there must be some reasonable expansion for it,
and the expansion is somewhat more reasonable when only the primary
value of the test-forms are returned. And I speculate that this could
be the historical reason why cond only returns the primary value of
test-forms.

--
Frode Vatvedt Fjeld

0 new messages