This is the mail archive of the guile@sourceware.cygnus.com mailing list for the Guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: values (Re: R5RS)




> > > (define values list)
> > > (define call-with-values apply)
> 
> Correction, this should have been
> 
> (define (call-with-values producer consumer)
>   (apply (producer) consumer))
>
> Could you please explain how exactly the implementation given
> above would break?

Well:
(car (values '(a b))) is a, not (a b).
(car (values 1 2)) is an error, not 1.
And (call-with-values (lambda ()
                        (call-with-current-continuation
                          (lambda (k) (k 2 3 4))))
                      list)
is (2 3 4), not an error.

I think multiple return values are ugly, too.

Basically, the Scheme authors are fascinated with the symmetry between
calling a function and returning from a function.  That's what call/cc
is all about: its existence proves that you can turn one into the
other.  That's why there are simply tons of early Scheme papers about
compiling via conversion to continuation-passing style, which is not
really very much better than compiling any other way, as far as I can
tell.  (But I could definitely be missing something here.)

What bothers the authors is that, while procedures can take as many
arguments as the user pleases, or even a variable number of arguments,
continuations can only accept one argument --- that is, you can only
return one value from a function.  So the symmetry is broken there.

The multiple values proposal restores this symmetry, by giving you a
way to create continuations which take more (or less!) than one
argument.  The values procedure is something you can write perfectly
in good old R4RS:

   (define (values . v)
     (call-with-current-continuation (lambda (k) (apply k v))))

The catch is, in R4RS, every k you'll ever get takes exactly one
argument.  So you can only apply values to a single argument, with
which it behaves like the identity function.  Not too interesting.

call-with-values is the one you can't write in R4RS.  When you
invoke it, (call-with-values THUNK CONSUMER), THUNK is called with a
continuation which takes as many arguments as CONSUMER does.  That is,
if you say

   (call-with-values (lambda ()
                       (call/cc (lambda (k) ... blah blah blah ...)))
                     (lambda (x y z)
                       ...))

you'll actually get a `k' there that wants three arguments.  And don't
forget:

   (call-with-values (lambda ()
                       (call/cc (lambda (k) ... blah blah blah ...)))
                     (lambda ()
                       ...))
   
In this one, you get a k that expects *no* arguments.  Weird, huh? 

Interesting?  Yeah.  Relevant?  Useful?  Gosh, no.  :)


Here's a portable implementation which is correct, but sometimes
yields a meaningless result when it should produce an error.  I think
I got the idea from slib.

(define values #f)
(define call-with-values #f)
(let ((unique-tag (list '*multiple-values*))
      (original-call/cc call-with-current-continuation))
  (set! values
    (lambda v
      (if (= (length v) 1) (car v)
          (cons unique-tag v))))
  (set! call-with-values
    (lambda (producer consumer)
      (let ((result (producer)))
        (if (and (pair? result)
                 (eq? (car result) unique-tag))
            (apply consumer (cdr result))
            (consumer result)))))
  (set! call-with-current-continuation
    (lambda (func)
      (original-call/cc
        (lambda (k)
          (func (lambda v (k (apply values v)))))))))

But this kind of misses the whole idea behind multiple values.

Look at it another way:

In C, if I have seven arguments I want to pass to a function, the C
ABI for my processor specifies how those seven values will get passed.
"These go in registers, floating-point values go in floating-point
registers, if you run out of registers then push any remaining values
on the stack, etc."  There are pretty elaborate rules for this.

The rules for returning values, however, are simpler.  "Most values go
in this register, but floating-point values go in this floating-point
register, and structures get returned in this weird way, etc."  Since
there's only one value to hand back, you avoid a lot of the hair that
necessarily appears in the rules for passing arguments.

But since you've gone to all the trouble of designing rules for
handling multiple values on the way in, why shouldn't you amend your
language allow multiple return values too, and use the same set of
rules for handling them on the way out?  You've already figured out
how to pack a tuple of arguments into registers; why not use those
same rules for passing a tuple of return values back?  Taking the time
to empower calls, but then crippling returns, seems stupid.

Anyway, ML handles this whole issue much more gracefully than Scheme.
Calls and returns are symmetrical: they both carry exactly one value.
But then they have very clear, terse ways to make that one value a
tuple.  Mission accomplished.  And no call/cc involved.


What I'd like is a way to call a function with only as many arguments
as it expects.  That is, I've got five arguments I could pass, but if
the function only takes three, that's cool, just pass the first three.
Then, I could apply this to `values', and return only as many values
as the caller expects.  So I could have a division function which
returns the remainder too, but only if the caller was prepared for two
return values.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]