; General ways to traverse collections, and the fate of call/cc ; ; The code for the article ; http://pobox.com/~oleg/ftp/Scheme/enumerators-callcc.html ; ; That article aims to consider various ways of traversing a collection, ; stressing the most complex and general case. That is, enumerating ; only a subset of a collection and performing actions that depend ; on the previously traversed nodes. This article also demonstrates ; a non-trivial use of call/cc -- to "invert" a traversal "inside out." ; In this application, call/cc _cannot_ be replaced with a simple ; throw/catch construction. A proposal is made for a convenient generic ; enumerator (which could be used, for example, in a truly Scheme ; ODBC interface). ; ; Suppose we have a collection with a for-each-type enumerator: ; a file system directory, an XML document, a suffix tree, ; or a "dynamic" collection that is the result of executing a ; SELECT DBMS/ODBC statement. Suppose we want to obtain the first ; 10 elements of the collection that satisfy a certain criterion: ; contain specific keywords, sound alike, what have you. ; ; For the purpose of this example, let's consider a collection ; of numbers of the form 65537x+5 mod 1997; The criterion will be ; a value being divisible by 7. To make the problem less trivial, let ; the collection be represented by a treap. We won't use the full ; power of the treap in this example: what matters to us is that it ; is an advanced collection, and it implements a for-each enumerator. ; ; $Id: enumerators-callcc-code-old.scm,v 2.1 2004/01/01 03:51:57 oleg Exp oleg $ (include "lib/myenv.scm") (include "lib/treap.scm") ; Make a collection to experiment with (cerr "Loading a treap to play with..." nl) (define treap (make-treap -)) (do ((i 0 (++ i))) ((>= i 1000)) ((treap 'put!) (modulo (+ (* i 65537) 5) 1997) i)) (cerr " treap's depth is " (treap 'depth) nl) ; (treap 'debugprint) ; A criterion in our running example (define (good-value? x) (zero? (modulo x 7))) (define pagesize 10) ; How many good numbers to seek ; This will serve as our baseline... (cerr nl "Printing out " pagesize " good numbers from the collection" nl " in a straightforward approach" nl) (define (get-good-numbers-dumb treap count-max) (let ((count 0) (result '())) ((treap 'for-each-ascending) (lambda (key-value) (let ((x (car key-value))) (and (< count count-max) (good-value? x) (begin (set! count (+ 1 count)) (set! result (cons x result))))))) (reverse result))) (cerr (time (get-good-numbers-dumb treap pagesize)) nl) ; Assignments are prominent in this solution: they are necessary to advance ; the state. It must be represented by global (to the iteratee) ; variables, which can persists from one invocation of the iteratee to the ; other. (cerr nl "Printing out " pagesize " good numbers from the collection" nl " in the straightforward approach with an early escape" nl) (define (get-good-numbers-cc treap count-max) (let ((count 0) (result '())) (call-with-current-continuation (lambda (escape) ((treap 'for-each-ascending) (lambda (key-value) (let ((x (car key-value))) (and (good-value? x) (begin (set! count (+ 1 count)) (set! result (cons x result)) (if (>= count count-max) (escape #f))))))))) (reverse result))) (cerr (time (get-good-numbers-cc treap pagesize)) nl) ; Lazy list: a promise, which when applied returns '() or ; (cons value lazy-list) ; data LazyList a = Promise (Nil | (Cons a (LazyList a))) (define (treap->lazy-list treap) (delay (call-with-current-continuation (lambda (k-main) ((treap 'for-each-ascending) (lambda (key-value) ; (cerr "\nIn treap->lazy-list's iteratee: " key-value "\n") (call-with-current-continuation (lambda (k-reenter) (let ((x (car key-value))) (k-main (cons x (delay (call-with-current-continuation (lambda (k-new-main) (set! k-main k-new-main) (k-reenter #f))))))))))) (k-main '()))))) ; Some implementations of Scheme can do forcing implicitly... (define (fcar x) (car (force x))) (define (fcdr x) (cdr (force x))) (define (fnull? x) (null? (force x))) (define lazy-treap-list (treap->lazy-list treap)) (cerr nl "First three elems of the treap: " (fcar lazy-treap-list) " " (fcar (fcdr lazy-treap-list)) " " (fcar (fcdr (fcdr lazy-treap-list))) nl) (cerr nl "Printing out " pagesize " good numbers from the collection" nl " by enumerating the lazy list" nl) (define (get-good-numbers-ll lazy-treap-list count-max) (let loop ((count 0) (llst lazy-treap-list) (result '())) (cond ((or (>= count count-max) (fnull? llst)) (reverse result)) ((good-value? (fcar llst)) (loop (+ 1 count) (fcdr llst) (cons (fcar llst) result))) (else (loop count (fcdr llst) result))))) (cerr (time (get-good-numbers-ll lazy-treap-list pagesize)) nl) ; Note a commented out line calling cerr procedure at the very ; beginning of the treap iteratee. If you uncomment that line ; you can see for yourself that iteratee is called only as many times ; as absolutely necessary during execution of get-good-numbers-ll. ; In all other respects lazy-treap-list looks like a regular list. ; This makes even complex, history-dependent traversal easily implementable, ; as lists can be walked in a variety of ways. The get-good-numbers-ll ; is a thinly-veiled finite state machine implemented in a pure-functional ; style. ; Performance-wise, the lazy list is definitely better than the dumb ; enumeration: the lazy list does not fetch the elements if they are ; not needed. Yet the lazy list loses out to the early escape. ; The get-good-numbers-ll procedure is purely functional: The state ; of the traversal is abstracted into a lazy pair, a first-class ; object. To advance the traversal, one has to _explicitly_ apply ; methods like fcdr (aka 'next'). One also has to explicitly check for EOF. ; In this respect, a lazy list is very similar to an STL ; iterator. OTH, a for-each enumerator does not give an iteratee ; any hint about the current state of the traversal, or its history. ; Procedure treap->lazy-list shows off call/cc at its best -- its ; ability not only to escape a scope but to re-enter it as well. ; treap->lazy-list cannot be implemented with simpler, throw-like ; constructions. ; There has been a discussion about the fate of call/cc. No doubt call/cc ; causes many a grief to implementors. Is this worth the trouble? The ; conclusions of this article are mixed. ; The early-escape solution uses call/cc only to throw a ; non-reenterable exception. This solution will work even if call/cc did ; not exist, being replaced by its weak relative, throw or raise. Only ; treap->lazy-list requires the full call/cc, to implement an ; enumeration-inversion trick. Is it worth it? Flexibility ; may be gained, yet some performance is lost.