Multiple-values SRFI Lassi Kortela 11 May 2020 09:09 UTC

> I will then submit a general multiple values SRFI before SRFI 195 is
> finalized (and maybe also before SRFI 189 is). This SRFI will include
> syntax like
>
> [...]
>
>>> (values->vector <expr>)
>>> (values-length <expr>)
>>> (values->list <expr>)
>>> (values-ref <expr> <index>)

+1. Very nice API. I've wished for something like this.

FWIW, the Common Lisp multiple values API is basically everything on the
page
<http://www.lispworks.com/documentation/HyperSpec/Body/c_data_a.htm>
with "value" in its name:

Macro MULTIPLE-VALUE-BIND
Special Operator MULTIPLE-VALUE-CALL
Macro MULTIPLE-VALUE-LIST
Special Operator MULTIPLE-VALUE-PROG1
Macro MULTIPLE-VALUE-SETQ
Accessor VALUES
Function VALUES-LIST
Constant Variable MULTIPLE-VALUES-LIMIT
Macro NTH-VALUE

NOTE: In CL, values-list means list->values, not values->list.

> I am not so sure about the naming; in view of SRFI 8, it could make
> sense to use names like `receive-list`, `receive-vector`. I am open to
> suggestions!

To me, the word "receive" connotes that it's a macro (a very nice macro,
to be sure); not sure how common this association is.

"Values" is a simpler, shorter word and since the term for the concept
is "multiple values", it's probably more obvious if the operator names
talk about "values" as well.

In Common Lisp I always regretted the excessively long operator names.
"multiple-value-bind" is the simplest and most common operation; as a
newbie it was difficult to find. Scheme's "receive" and "let-values" are
a big improvement in that sense.

> Such a SRFI will also include procedures that go the other way, like
>
> (list->values <list>)
>
> which is equivalent to (apply values <list>), an often-used idiom, but
> may be implemented more efficiently.

+1, very useful!

> Again, there may be better names, like `unpack-list` or `explode-vector`.

I really like the names you chose. They are symmetrical among themselves
and with the rest of Scheme, and use common words that are familiar to
every Schemer.

> Racket has `set!-values`
> (https://docs.racket-lang.org/reference/set_.html#%28form._%28%28lib._racket%2Fprivate%2Fmore-scheme..rkt%29._set%21-values%29%29),

Nice. `define-values` is already in the standard so `set!-values` is a
natural complement. In Common Lisp, MULTIPLE-VALUE-SETQ.

> which can slightly be generalized to also take formals with a rest
> argument, and which also should go into such a multiple-values SRFI.

Even nicer. Would this be best done using the consing dot?

> Moreover, we have case-lambda that dispatches on the number of
> arguments an ordinary procedure receives; we don't have a similar
> construct for the number a continuation receives (and which could be
> useful to implement some of SRFI 189's conversion procedures whose
> protocol depends on the number of values). Therefore, I would also
> propose
>
> (case-receive <expr>
>    (<formals> <body>) ...)
>
> which can be rewritten into
>
> (with-values <expr>
>    (case-lambda
>      ((<formals> <body>) ...)
>
> Here, `with-values` is of course
>
> (define-syntax with-values
>    (syntax-rules ()
>      ((with-values expr receiver) (call-with-values (lambda () expr) receiver))))
>
> (If I remember correctly, Kent Dybvig once made a point that
> `with-values` should have been standardized, not `call-with-values`
> because both are equivalent in expressiveness but it easier to work
> with an optimize `with-values`.)

LGTM.

> Another part of the language, where everyone could benefit from a
> better support of multiple values, are various higher-order
> procedures. For example, SRFI 1 defines `fold` and `fold-right`.
> `fold` is the fundamental list iteration operator and `fold-right` is
> the fundamental list recursion operator. Code that uses them is
> conceptionally clearer than code using hand-written loops with
> named-let. The problem is, however, when more than one value has to be
> threaded through the iteration or recursion.
>
> Most of the time, the user will have to go back to hand-written loops
> but will then have to deal with `values` and `let-values` explicitly.

Definitely.

> However, there is no obvious generalization of `fold` and friends to
> multiple values (which implies multiple seeds). The problem is the
> calling convention of the procedure that is being called in each step.
> Currently, it takes the accumulator and an arbitrary number of values
> corresponding to the number of lists (vectors, generators, gadgets,
> ...) being iterated or recursed over. A multiple-value version `fold*`
> would have to apply an arbitrary number of accumulator values and an
> arbitrary number of values corresponding to the number of lists
> present but there is only one rest argument.

IMHO the most obviously useful is (fold* step list init...) where N init
values mean `step` takes N accumulators and returns N values to use as
the next accumulators. At the end, `fold*` returns N values.

Using multiple-value boxes, we could also do (fold* step init list...)
where `init` is a box. `step` would then be called with M lists and N
accumulators (in either order) and return N accumulators as values.

Multiple-value boxes would work for an even more general fold* if we
pass the accumulators to `step` as boxes. This may actually be less
crazy than it sounds :)

Perhaps instead of the most generic values-fold there should be a
box-fold. In Scheme implementations with multiple-value boxes, box-fold
could take advantage of them.

M-v boxes a nice generalization that doesn't make the existing
single-value boxes any more difficult for users. If they are easy to
implement efficiently, they will hopefully be widely adopted.

> One solution would be to make `fold*` into syntax, say:
>
> (fold* (<list-formals> <seed-formals> <body>) (<seed> ...) (<list> ...)),
>
> which would work unless anyone would want to use `fold*` itself in any
> higher-order context (are there any use cases for that?).

Perhaps higher-order folds are usually done by packaging the lower-level
ones into library procedures, so the inner fold is an implementation
detail that is not visible to the outer one.

> Alternatively, `fold*` is an ordinary higher-order procedure receiving
> a procedure that evaluates into another procedure first, e.g. like the
> following or similarly:
>
> (fold* (lambda <list-formals> (lambda <seed-formals> body)) (lambda ()
> (values <seed> ...)) <list> ...)

Would it work to use m-v boxes instead (as an abstraction built
specifically for the purpose)?

> Or we could restrict `fold*` to a single list, but this would restrict
> its usefulness.

We should probably have that one in any case.

(fold* (lambda (elem acc1 acc2) ... (values new-acc1 new-acc2))
        list init1 init2)

is useful and quite easy to understand.

> I would love to hear your suggestions, especially about further
> procedures and syntax that should be included.

You nicely covered all of the essentials :)