Tue Jun 24 10:30:59 CEST 2008

playing with generators

A problem that i've run into is 'wrapping' a sequence around a loop to
build a 2D view. It pops up more than once (i.e. list->table), so lets
make an abstraction for it.

This is trivial to solve with a generator + comprehension:

     (for ((row  (in-naturals)))
        (printf "~a \n" row)
        (for ((columns (in-range 8)))
           (printf "~a " (generate)))
        (printf "\n"))

But the problem here is termination condition. Can this be turned into
a comprehension that abstracts termination?

A simple way is to turn the printing loop itself into a generator:

(define printer
 (lambda (yield)
   (for ((row (in-naturals)))
      (yield (lambda (x) (printf "~a ~a" (* row 8) x)))
      (for ((i (in-range 6))) (yield (lambda (x) (printf " ~a" x))))
      (yield (lambda (x) (printf "~a\n" x)))))))

which can then be easily combined with the sequence to be printed

(for ((p printer))
     ((i sequence))
  (p i))

The only thing that's awkward here is the missing newline in case the
sequence terminates in the middle of a line. This could be solved by
using some 'strings with backspace'.

This is the cleaned-up hex printer sequence:

(define (in-hex-printer [start 0]
                        [data-nibbles 2]
                        [address-nibbles 4])
   (lambda (yield)

     (define-syntax-rule (lp formals . args)
       (yield (lambda formals (printf . args))))
     (define (addr x) (hex->string address-nibbles x))
     (define (data x) (hex->string data-nibbles x))

     (for ((row (in-naturals start)))
       (lp (x) "~a  ~a" (addr (* row 8)) (data x))
       (for ((i (in-range 6))) (lp (x) " ~a" (data x)))
       (lp (x)  " ~a\n" (data x))))))

Which can be used as in:

(define (slurp)
  (for ((i (in-thunk in))
        (p (in-hex-printer))) (p i)))

Now, turn it into an enumerator + a sequence derivative. Does that
makes sense? What does a fold look like if the print output is seen as
a data structure? It actually makes more sense as unfold operation.

So, really, print is a consumer, but i turned it into a consumer

Moral of the story?

  * comprehensions abstract termination conditions which makes them
    easier to use than generators (eof?/read)

  * in cases where generators are more convenient than nested/parallel
    loops (parsing/printing-style data representation conversion
    problems), the consumer can be turned into a a squence of consumer
    procedures, which can then be linked to a producer sequence in a
    simple for loop.