(missing)
Re: Fwd: Make code safe for continuation capture, part the first. · gambiteer/srfi-231@2876863 Bradley Lucier (03 Aug 2022 22:12 UTC)

Re: Fwd: Make code safe for continuation capture, part the first. · gambiteer/srfi-231@2876863 Bradley Lucier 03 Aug 2022 22:12 UTC

On 8/3/22 5:33 PM, Marc Nieper-Wißkirchen wrote:
>
> ---------- Forwarded message ---------
> Von: *Marc Nieper-Wißkirchen* <xxxxxx@gmail.com
> <mailto:xxxxxx@gmail.com>>
> Date: Mi., 3. Aug. 2022 um 23:19 Uhr
> Subject: Re: Make code safe for continuation capture, part the first. ·
> gambiteer/xxxxxx@2876863
> To: John Cowan <xxxxxx@ccil.org <mailto:xxxxxx@ccil.org>>
>
> Am Mi., 3. Aug. 2022 um 22:29 Uhr schrieb John Cowan <xxxxxx@ccil.org
> <mailto:xxxxxx@ccil.org>>:
>
>     On Wed, Aug 3, 2022 at 11:48 AM Bradley Lucier
>     <xxxxxx@math.purdue.edu <mailto:xxxxxx@math.purdue.edu>> wrote:
>
>         In the example I gave previously, this can result in much greater
>         temporary memory use, up to 192 times in Gambit for bit arrays
>         (which
>         Gambit reports as 384 times because it has a stop-and-copy garbage
>         collector, so it reports both space allocated in the old heap
>         and space
>         that it might need in the new heap if that structure survives GC).
>
>
> Can you explain the space cost?  In procedures like `vector-map` at most
> one copy is needed when `vector-map` returns so the space cost can be at
> most twice.  The actual cost can be much lower, depending on the quality
> of the GC.

Each forward iteration of vector-map remembers (1) the index, (2) the
computed value of an entry with that index and (3) the address where to
return the vector result.  These three words per iteration sit on the
stack.  Because it's a bit more complicated to iterate through an array,
I chose to use a list (with three words per pair).  I do use an extra
word to put these values in a new specialized array, to make the mapping
between multi-index and item easier.

> Don't use a parameter.  Export the procedures under two different names
> (or under the same name but in two different sub-libraries).  A
> parameter wouldn't make much sense here.

"A parameter wouldn't make much sense here" is not much of an argument,
because I just said, basically, "A parameter would make sense".  Want to
explain further?

I'm committed to at least finishing a call/cc-friendly version of the
library, you've convinced me of that.

Perhaps someone can offer some advice.  I just added a call/cc-friendly

(interval-foldl f operator identity interval)

The call/cc-unfriendly version for large dimensions is

(let* ((lower-bounds (%%interval-lower-bounds->list interval))
                 (upper-bounds (%%interval-upper-bounds->list interval))
                 (arg          (map values lower-bounds)))
   ; copy lower-bounds

            ;; I'm not particularly happy with set! here because f or
operator might capture
            ;; the continuation and then funny things might pursue ...
            ;; But it seems that the only way to have this work
efficiently without the set~
            ;; is to have arrays with fortran-style numbering.
            ;; blah

            (define (iterate lower-bounds-tail
                             upper-bounds-tail
                             arg-tail
                             result)
              (let ((lower-bound (car lower-bounds-tail))
                    (upper-bound (car upper-bounds-tail)))
                (if (null? (cdr arg-tail))
                    (let loop ((i lower-bound)
                               (result result))
                      (if (= i upper-bound)
                          result
                          (begin
                            (set-car! arg-tail i)
                            (loop (+ i 1)
                                  (operator result (apply f arg))))))
                    (let loop ((i lower-bound)
                               (result result))
                      (if (= i upper-bound)
                          result
                          (begin
                            (set-car! arg-tail i)
                            (loop (+ i 1)
                                  (iterate (cdr lower-bounds-tail)
                                           (cdr upper-bounds-tail)
                                           (cdr arg-tail)
                                           result))))))))

            (iterate lower-bounds
                     upper-bounds
                     arg
                     identity))

This is somewhat space friendly.  The best I could come up with for a
call/cc-friendly version is:

           (let ()

             (define (get-next-args reversed-args
                                    reversed-lowers
                                    reversed-uppers)
               (let ((next-index (+ (car reversed-args) 1)))
                 (if (< next-index (car reversed-uppers))
                     (cons next-index (cdr reversed-args))
                     (and (not (null? (cdr reversed-args)))
                          (let ((tail-result (get-next-args (cdr
reversed-args)
                                                            (cdr
reversed-lowers)
                                                            (cdr
reversed-uppers))))
                            (and tail-result
                                 (cons (car reversed-lowers)
tail-result)))))))

             (let ((reversed-lowers (reverse
(%%interval-lower-bounds->list interval)))
                   (reversed-uppers (reverse
(%%interval-upper-bounds->list interval))))
               (let loop ((reversed-args reversed-lowers)
                          (result identity))
              ;;; There's at least one element of the interval, so we can
              ;;; use a do-until loop
                 (let ((result (operator result (apply f (reverse
reversed-args))))
                       (next-reversed-args (get-next-args reversed-args
                                                          reversed-lowers
                                                          reversed-uppers)))
                   (if next-reversed-args
                       (loop next-reversed-args result)
                       result)))))

Because we're working with row-major (C) order, where the rightmost
index moves fastest, and we can only add and remove things from the left
of the list, I have the call

(operator result (apply f (reverse reversed-args)))

If we used column-major (Fortran) order, then I wouldn't need to reverse
the computed arg list before applying f.

But if we used column-major order, then currying an array would be
somewhat counterintuitive for a Lispish programmer.  On the other hand,
except for previous versions of this SRFI, I know of no other array
library that has general array-curry, so maybe it would be OK.

I decided to macrofy the code generation for small dimensions, so
straightforward loops are now used in interval-foldl up to dimension 8,
so this general code will hardly ever be used. (But it has been tested.)

Can someone offer advice on how to recode this loop without using
reversed arguments and bounds?

Brad