Re: Introspection Marc Nieper-Wißkirchen 11 May 2020 07:11 UTC

Thanks for chiming in, Wolfgang!

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>)

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!

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.

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

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),
which can slightly be generalized to also take formals with a rest
argument, and which also should go into such a multiple-values SRFI.

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`.)

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.

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.

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?).

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> ...)

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

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

Marc

P.S.: Some SRFI should also describe `(delay <expr>)` when <expr>
evaluates to an arbitrary number of values and the corresponding
`(force <expr>)` and `(make-promise <expr> ...)`. This extension is
necessary for programs that want to employ both lazy semantics and
multiple values that are present in Scheme's evaluation model. (SRFI
45 describes a recipe to convert an algorithm with lazy semantics to
Scheme with explicit use of the lazy primitives. As soon as multiple
values are present, this conversion won't work anymore. When I wrote
SRFI 155, I should have already noted it but I didn't, so I will have
to revise it.)