Re: Multiple-values SRFI Lassi Kortela 11 May 2020 10:52 UTC

> The point is that values->list (or receive-list or however we are
> going to name it) has to be syntax (e.g. implemented as a macro)
> because of the limitations of Schemes applicative syntax.

Sorry, I was sloppy with terminology. While (values->list (foo)) is
indeed syntax, it's very lightweight and looks like an ordinary
procedure call. (receive (a b c) (foo) body...) looks very different
because the body is included.

> "multiple values" are no first-class concept (unless you reify
> them with multiple value boxes). A procedure named `foo->bar` should
> transform objects of type `foo` into objects of type `bar`. This
> reading does not work if `bar` is replaced by the word `values`.

Very good point. Maybe that kind of pun is not clean enough :) One of
Scheme's best aspects is the consistent attention to detail.

> Maybe, replacing "->" by "-" helps here. So `list-values` and
> `vector-values`.

The trouble with using only a hyphen is that it's not obvious which way
the conversion is done. Common Lisp's `values-list` turns a list into
multiple values, but its name sounds like it would do the opposite. The
opposite operator, `multiple-value-list`, sounds like an alias instead
of an opposite.

It could be named `values-from-list`, and there could be a matching
`list-from-values`. But that's arguably just a clever way of writing
`list-to-values` and `values-to-list` in reverse. And `-to-` sounds like
a synonym of `->` so it would be confusing to add such names to Scheme.

In that sense `receive-list` would be more obvious. But `receive`
doesn't have an established antonym in this context. IMHO `send` sounds
too fancy and is easy to confuse with concurrency or networking stuff.

`let` has the same problem (?) as `receive` in that people may expect it
to have a body. The word `bind` (as used by Common Lisp) is reasonable,
but CL's `multiple-value-bind` also has a body.

>>> Racket has `set!-values`
>>> 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?
>
> Yes. And there should be a reference to SRFI 17 for Schemes that
> implement it. (NB: But we should remember that there are also reasons
> for not promoting the idea of SRFI 17:
> https://srfi-email.schemers.org/srfi-17/msg/2778583/)

Good idea.

>> 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.
>
> We should see whether this protocol can be implemented efficiently.
> However, I don't think that the number of values passed in each
> iteration step should be forced to be fixed. The next iteration should
> receive as many values as the previous one produced. (BTW, this
> requirement rules out my simplest syntax proposal.)

That would require something like a `case-lambda` for the step
procedure. Would there be any practical problem that is most
productively solved in this way?

Multiple-value boxes offer an obvious way to pass a different number of
values at each step, though not sure about the efficiency.

>> 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.
>
> So you mean that the iteration/recursion procedure has to unbox the
> seed by hand and the multiple values it returns will be auto-boxed for
> the next iteration by `fold*`?

Yes, exactly. Again, this is probably not that efficient :)

> I will add a system-specific implementation (for Chibi) to show how to
> implement them efficiently when the system already packages multiple
> values into some internal structure.

Great.

>>> 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?).
>
> The problem I have just seen here is that <seed-formals> is fixed in
> size, forcing the same number of values passed in each step (which is
> a bit of an unnatural requirement).

>>> 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)?
>
> The syntax may be simplified. Boxing, however, adds another level of
> indirection, so boxing both seeds and lists may be a bit too much.

It's not clear how to avoid indirection. An inner lambda adds
indirection as well. Perhaps inner lambdas are easier to eliminate by
existing Scheme inliners.

>> We should probably have that one in any case.
>>
>> (fold* (lambda (elem acc1 acc2) ... (values new-acc1 new-acc2))
>>          list init1 init2)
>
> Indeed; this will be used often and should be simple. For hash-tables,
> this is all we would need (it does not make sense to process unordered
> lists in parallel).

Good point.

> In order to keep the dependencies of this planned SRFI below a certain
> limit, I would propose that procedures like
>
> hash-table-fold*, generator-fold*, stream-fold*, ifold*, ...
>
> go into their respective SRFI libraries (taking the same approach as
> SRFI 162, which amended the SRFI 128 library) once we have agreed on
> the signature.

Sounds reasonable.

> We also want to talk about `unfold*` and friends. For example, there
> is currently no idiomatic way to create a SRFI 125 hash table from a
> list of keys and a list of values (values not in the mv sense :)).
> While `alist->hash-table` can be defined through `hash-table-unfold`,
> this doesn't work with separate lists of keys and values because
> `hash-table-unfold` is restricted to a single seed.

Something like the following?

(define (lists->hash-table ks vs)
   (unfold* (values ks vs)
            (lambda (ks vs) (and (pair? ks) (pair? vs)))
            (lambda (ks vs) (values (cdr ks) (cdr vs)))
            (make-hash-table)
            (lambda (hash-table k v)
              (hash-table-set! hash-table k v)
              hash-table)))