FORWARD #2

---------- Forwarded message ---------
Von: Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de>
Date: Mi., 1. Sept. 2021 um 14:38 Uhr
Subject: Re: SRFI 228: A further comparator library
To: Daphne Preston-Kendal <xxxxxx@nonceword.org>


Am Mi., 1. Sept. 2021 um 14:08 Uhr schrieb Daphne Preston-Kendal <xxxxxx@nonceword.org>:
On 1 Sep 2021, at 11:04, Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:

>> Yes, I agree. I’ll revise it as such. I’m also unsure whether compose-comparator should even be syntax in the first place though. It’s a convenience around make-wrapper-comparator and make-composed-comparator, but I’m not sure it’s so convenient that it actually needs to be in the SRFI.
>
> Do you have some example code so that one can compare the code needed to be written with and without?

Syntactic:

(compose-comparator date?
  (date-year)
  (date-month)
  (date-day))

Procedural:

(make-composed-comparator date?
  (make-wrapper-comparator date? date-year default-comparator)
  (make-wrapper-comparator date? date-month default-comparator)
  (make-wrapper-comparator date? date-day default-comparator))

I see that the difference won't be very big for production code that would abstain from using the default comparator if possible.  (That "compose-comparator" uses the default comparator as a default is probably not such a good idea because it will lead inexperienced users to write inefficient, non-type safe code when they could do better).
 
> My proposal would be more flexible as it could also, for example, be used in local binding constructs:
>
> (let-values (((date<? date>?) (comparison-procedures my-date-comparator < >)))
>   ...)
>
>>> > (4) Can you give a rationale why "comparison-procedures" is a useful abstraction when compared with a version that returns the comparators individually?
>>
>> I don't understand what you mean by ‘returns the comparators individually’. Could you explain?
>
> Something operationally equivalent to
>
> (define (comparator-predicate<= comparator)
>   (lambda arg*
>     (apply <=? comparator arg*)))

Ah! I assumed a particular use-case when designing it — specifically, defining all five procedures for a given comparator. But perhaps there are cases where only having some of them would be handy.

That would be solved with my syntax proposal (which also gets rid of a particular way of ordering the predicates).
 
>> You give it the type test for a data structure and tell it how to convert from that data structure into a generator.
>> So (make-generator-comparator list? list->generator the-comparator-for-the-individual-items).
>
> Thanks, I understand now.  I would prefer a more functional style with "fold" (which you also mention), though.

I found a semantic problem with using fold as the basis of this which seemed to me to be insurmountable. I have since forgotten what it was, though — I’ll take another look and get back to you.

Thanks.  I would like to hear about it.
 
(Another problem with using fold procedures is the conflict between list- and vector-style fold procedures, as mentioned on my Ultraviolet issues list page. When I was still considering a fold-based version, John Cowan gave his blessing to the following solution: SRFI 228 will assume that all folds are list-style; the sample implementation uses a magic trick to detect when it's been given a vector-style fold and knows to treat it as a list-style fold; the specification text will note this trick and say that implementations are not required to implement it, expressing hope that R7RS Large will fix the folds to be consistent; if SRFI 228 makes it into R7RS Large, it then becomes an Ultraviolet issue to define a version of make-fold-comparator which works with vector-style folds if Ultraviolet doesn't change them to list-style folds. I’d now be inclined to do it the other way around, though, and standardize on the vector-style fold. More of this in another thread (on srfi-discuss?) if desired.)

Yes, we can move the fold discussion to another thread.  A final fold API template should also include a way to specify multiple seeds (at least in those situations where exactly one sequence is involved).
 
> Even better than "fold" (which would need a non-local escape) would be an iterator (as the functional version of a generator, see SRFI 226 for one possible incarnation).
>
> (define (make-iterated-comparator type-test iterator element-comparator)
>   ...)
>
> Here, ITERATOR could be a procedure taking two arguments, SUCCEED and FAIL.  When the iterator is called and the underlying sequence is not empty, it tail-calls SUCCEED with two values, the first element and an iterator representing the rest of the sequence.  Otherwise, FAIL is tail-called with no argument.
>
> Besides being functional, this has the important advantage over generators that "list->generator" and friends will fail as soon as the list (or any other sequence) contains an eof object.  I see this as a severe defect of generators when they are used in a general context.  Generators are not suitable when arbitrary objects are processed; one needs a prior knowledge that the objects will all be different from eof objects.

Yes, I too see this as a severe flaw of the SRFI 158 design, but alas I was not around at the time to argue against it. I would have, at the very least, made generators one-argument procedures rather than thunks, and had them return the argument passed when the sequence is finished, instead of the eof-object. Or had them return two values, the extra one being #t if there are more items in the sequence and #f if not.

But this is what we have.

But it doesn't mean that one should use or promote the concept more than necessary.  Generators are fine where one has control over the values (and has imperative code), just as in the prototypical generator, namely "read".  But they are unsuitable for, say, SRFI 228 to handle arbitrary sequences because of eof-problem.  Likewise, they are not suitable for providing glue code between different general-value sequence libraries.
 
Your proposal seems to aim in a similar direction, but seems somewhat more fiddly to me. It also probably requires consing on each step of the iteration, no? (Even if this is ‘hidden’, from the point of view of the design of this iterator pattern, in the user-defined succeed procedure.) The point of SRFI 158 is to avoid that inefficiency.

Your proposal doesn't remove the imperative nature.

In what sense do you think my proposal is more fiddly?

(define (generator->list gen)
  (let f ((val (gen))
    (if (eof-object? val)
        '()
        (cons val (f (gen)))))

(define (iterator->list it)
  (let f ((it it))
    (it (lambda (it val)
          (cons val (f it)))
        (lambda () '()))))

[Add any convenience syntax if you don't like explicit lambdas.]

I don't see any consing here (except for the explicit "cons").  (And the closures of the two lambdas are constant.)

Marc