[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Multiple Values: An Opinion



I'm sorry, I know I'm not supposed to be on this line, but I just had
to state my opinion of multiple values. I think this is one (!) of the
most poorly thought out (and most haphazardly integrated) concepts in 
common lisp. I'd hate to see scheme suffer from the same disease.

First of all, I see no need for the construct whatsoever. Each of
the functions to deal with multiple values can be replaced by existing 
scheme functions:

(values <v1> ... <vn>) => (list <v1> ... <vn>)

(receive-values <fn> <mv-thunk-or-1st-class-mv-obj>) => (apply <fn> <list>)

(single <mv-thunk-or-1st-class-mv-obj>) => (car <list>)

(multiple-value-set! (<id1> ... <idn>) <mvs>) =>
	(map set! '(<id1> ... <idn>) <list>)

Therefore, the multiple value constructs add no new functionality to the
language (shame). They are simply included to increase lisp's efficiency
(a job which is best left up to the compiler). In light of todays cdr-coded
lists I would think consing can be accomplished as efficiently as vector
allocation for multiple-value objects. I would assume the same tricks could
be employed for register or stack allocation of some lists, as they are 
returned to functions which immediately destructure them.

All of your efficiency considerations still hold:

Efficiency considerations 
-------------------------

A compiler/runtime strategy could be to return lists spread out on the
stack or in registers.  When lists are returned to a context which
expects them the corresponding list object would never have to be
reified.  For example, in

     (define mvs (lambda (a b c) (list a b c)))

     (let* ((m (mvs 1 2 3))
	    (x (first m))
	    (y (second m))
	    (z (third m)))
       ...)

a list object would not be allocated.  List objects would only
have to be allocated in the heap when lists are returned to a context
which is not expecting a list.  For example,

     (cons (list <v0> ... <vN> ) ...)

would require a list object to be allocated.  However, a smart
compiler could avoid allocating a list object in many situations where
lists are indirectly expected.  For instance, if the equivalent of
Common Lisp's (multiple-value-prog1 <exp1>... <expN>) was implemented
as:

     (let ((x <exp1>))
       <exp2> 
       ... 
       <expN> 
       x)

a smart compiler could avoid the allocation of the list bound
to x by:

    1) Leaving the list returned by <exp1> spread out on the
       stack. 
    2) Evaluating <exp2> through <expN>.
    3) returning the spread out list of <exp1>.

Similar optimizations could be applied to lists returned by
know continuations, like cont, in:

	(call/cc
	   (lambda (cont)
	      ...
	        (cont (list <v0> ... <vN>))
	      ...))

where the presence of the list in argument position would normally
require a list object to be allocated.

-----

     Lets work at optimizing the compiler, not giving the programmer
his own hooks to optimize. I think most people avoid multiple values
anyway.

Warren Harris
HP Labs, bld 3U
1501 Page Mill Rd.
Palo Alto, CA 94040
harris%hplwhh@hplabs.HP.COM

P.S. How about a nice destructuring facility similar to multiple-value-bind
except for lists (like zetalisp's destructuring-bind). For example, what 
could be implemented with multiple values as:

	(defun foo (x y z) (values x y z))

	(multiple-value-bind (x y z) (foo 1 2 3))

or regular lists as:

	(defun foo (x y z) (list x y z))

	(let* ((all (foo 1 2 3))
	       (x (first all))
	       (y (second all))
	       (z (third all)))
	  ...)
	
could be more concisely stated as:	

	(destructure (x y z) (foo 1 2 3)
	  ...)

Also, arbitrary trees could be destructured:

	(destructure ((x . y) z) '((a b c d) e)
	  (print y))

	=> (b c d)

This would help the compiler in its optimization, as well as give the 
programmer a succinct syntax for a routine procedure.
-------