(module generator mzscheme (require-for-syntax (lib "contract.ss")) (require (lib "contract.ss") (lib "etc.ss") (only (file "project.ss") partition/mask) (only (file "yield.ss") yieldable) (file "base.ss")) ; There is no doubt that lists are useful structures for representing ; many kinds of data, and that folds and maps are a quick, convenient ; way of performing arbitrary bits of list manipulation. ; ; The main problem with the list/fold/map approach is the number of ; temporary lists generated in the process, which can take up a large ; amount of memory. ; ; Generators are a half-way-house between lists and streams that aim ; to reduce memory overhead when large data sources are involved. ; ; A generator is a stream-like accessor that can be repeatedly called ; to return new values from its source. A special "generator-end" value ; is returned to indicate that the source has been exhausted. ; ; For convenience we write a generator of a type "a" as follows: ; ; (gen-> a) === (-> (U a generator-end)) ; ; This library provides convenient ways of: ; ; - producing generators from lists ; - combining generators to form other generators ; (c.f. fold, map and so on) ; - accumulating results from generators ; (e.g. back into lists) ; Variables ------------------------------------ ;; generator-end : symbol (define generator-end (gensym 'generator-end)) ; Syntax --------------------------------------- ;; syntax (gen-> contract) ;; ;; Expands into a contract that works with values and the generator-end ;; symbol. (define-syntax (gen-> stx) (syntax-case stx () [(_ expr) #'(-> (or/c expr generator-end?))])) ; Core procedures ------------------------------ ;; generator-end? : any -> boolean (define (generator-end? item) (eq? item generator-end)) ;; generate-all : (list-of (gen-> any)) -> (list-of any) (define (generate-all gens) (map (lambda (item) (item)) gens)) ; Combinators ---------------------------------- ;; generator-map : (a b c ... -> d) (gen-> a) (gen-> b) (gen-> c) ... -> (gen-> d) ;; ;; The generator equivalent of "map" from SRFI 1. ;; ;; Given a mapping function "fn" and some sources, creates a generator that returns: ;; ;; (apply fn sources) ;; ;; If, in a given iteration, any of the sources return generator-end, the mapping ;; function is not called, and the generator simply returns generator-end. (define (generator-map fn . gens) (let ([id (gensym)]) (lambda () (let ([args (generate-all gens)]) (if (ormap generator-end? args) generator-end (apply fn args)))))) ;; generator-fold : (a b c ... k -> k) k (gen-> a) (gen-> b) (gen-> c) ... -> (gen-> k) ;; ;; The generator equivalent of "fold" from SRFI 1. ;; ;; Given an iterator function "it", an initial accumulator and some sources, ;; creates a generator that returns: ;; ;; (apply it (append sources (list accum)). ;; ;; The result is stored after each iteration and used as the accumulator for the ;; next iteration. ;; ;; If, in a given iteration, any of the sources return generator-end, the iterator ;; function is not called, and the generator simply returns generator-end. (define (generator-fold proc accum . gens) (lambda () (let ([args (generate-all gens)]) (if (ormap generator-end? args) generator-end (begin ; Update the accumulator... (set! accum (apply proc (append args (list accum)))) ; ...and return it. accum))))) ;; generator-filter : (a -> boolean) (gen-> a) -> (gen-> a) ;; ;; The generator equivalent of "filter" from SRFI 1. ;; ;; Given a predicate "pred" and a source, creates a generator that returns ;; only those source values for which: ;; ;; (pred source) ;; ;; is non-#f. Note that this means that a single call to the generator can result ;; in multiple calls to the source. ;; ;; If, in a given iteration, the source returns generator-end, the iterator ;; function is not called, and the generator simply returns generator-end. (define (generator-filter test gen) (letrec ([ans (lambda () (let ([arg (gen)]) (cond [(generator-end? arg) generator-end] [(test arg) arg] [else (ans)])))]) ans)) ;; generator-filter-map : (a -> (U any #f)) (gen-> a) -> (gen-> any) ;; ;; The generator equivalent of "filter-map" from SRFI 1. ;; ;; Given a predicate "pred" and a source, creates a generator that returns non-#f ;; values of: ;; ;; (pred source) ;; ;; Note that this means that a single call to the generator can result in ;; multiple calls to the source. ;; ;; If, in a given iteration, the source returns generator-end, the iterator ;; function is not called, and the generator simply returns generator-end. (define (generator-filter-map test gen) (letrec ([ans (lambda () (let ([arg (gen)]) (if (generator-end? arg) generator-end (let ([answer (test arg)]) (if answer answer (ans))))))]) ans)) ;; generator-remove-duplicates : (gen-> a) -> (gen-> a) (define generator-remove-duplicates (let ([empty (gensym)]) (opt-lambda (gen [same? equal?]) (let ([last empty]) (lambda () (let loop ([curr (gen)]) (cond [(generator-end? curr) generator-end] [(same? last curr) (set! last curr) (loop (gen))] [else (set! last curr) curr]))))))) ;; generator-debug : string (gen-> any) -> (gen-> any) ;; ;; Creates a generator that mimics its source, but prints generated values ;; as it goes. (define (generator-debug message generate) (lambda () (let ([item (generate)]) (printf "~a ~a~n" message item) item))) ; Accumulators and list interoperability ------- ;; generator-for-each : (a b c ... -> void) (gen-> a) (gen-> b) (gen-> c) ... -> void ;; ;; Repeatedly calls source generators, supplying their values to an iterator ;; procedure, until one or more returns generator-end. (define (generator-for-each proc . gens) (let ([args (generate-all gens)]) (if (ormap generator-end? args) (void) (begin (apply proc args) (apply generator-for-each (cons proc gens)))))) ;; generator-accumulate : (a b c ... k -> k) k (gen-> a) (gen-> b) (gen-> c) ... -> k ;; ;; Another equivalent of "fold" from SRFI 1. ;; ;; Given an iterator function "it", an initial accumulator and some sources, ;; repeatedly does: ;; ;; (apply it (append sources (list accum)) ;; ;; until one or more of the sources returns generator-end. At this point the ;; accumulator is returned. (define (generator-accumulate proc accum0 . gens) (let loop ([accum accum0]) (let ([args (generate-all gens)]) (if (ormap generator-end? args) accum (loop (apply proc (append args (list accum)))))))) ;; list->generator : (list-of a) -> (-> (U a generator-end)) ;; ;; Creates a generator that iterates through the values in data and then ;; repeatedly returns end. (define (list->generator data) (lambda () (if (null? data) generator-end (begin0 (car data) (set! data (cdr data)))))) ;; generator->list : (gen-> a) -> (list-of a) ;; ;; A convenient form of generator-accumulate that collects generated values ;; into a list. (define (generator->list gen) (reverse (generator-accumulate cons null gen))) ; Snooze specific (TODO : move to Snooze) ------ ;; generator-project ;; : (list-of boolean) ;; (gen-> (list-of a)) ;; [(a a -> boolean)] ;; -> (gen-> (append (list-of a) (list-of (list-of a)))) ;; ;; Projects items from the supplied generator according to the rules ;; set out in project.ss. ;; ;; Passes non-list items straight through. (define generator-project (opt-lambda (mask generate [same? eq?]) (define (projectable? x) (or (pair? x) (null? x))) (define collect-nonkeys (case-lambda ((next-nonkeys nonkeys-accum) (if (andmap not next-nonkeys) nonkeys-accum (cons next-nonkeys nonkeys-accum))) ((next-nonkeys) (collect-nonkeys next-nonkeys null)))) (yieldable yield (define (yield* last-keys last-nonkeys) (yield (append last-keys (list (reverse last-nonkeys))))) (lambda () (let*-values ([(last) (generate)] [(keys0 nonkeys0) (if (list? last) (partition/mask last mask) (values #f null))]) (let loop ([last last] [last-keys keys0] [last-nonkeys (collect-nonkeys nonkeys0)] [next (generate)]) (let-values ([(next-keys next-nonkeys) (if (projectable? next) (partition/mask next mask) (values #f null))]) (if (projectable? last) (if (projectable? next) (if (andmap same? last-keys next-keys) (loop next next-keys (collect-nonkeys next-nonkeys last-nonkeys) (generate)) (begin (yield* last-keys last-nonkeys) (loop next next-keys (collect-nonkeys next-nonkeys) (generate)))) (begin (yield* last-keys last-nonkeys) (loop next #f null (generate)))) (if (projectable? next) (begin (yield last) (loop next next-keys (collect-nonkeys next-nonkeys) (generate))) (begin (yield last) (loop next #f null (generate)))))))))))) ; Provide statements --------------------------- ; "Normal" versions: (provide gen-> generator-end generator-end?) (provide/contract [generator-map (->* (procedure?) (listof procedure?) (procedure?))] [generator-fold (->* (procedure? any/c) (listof procedure?) (procedure?))] [generator-filter (-> procedure? procedure? procedure?)] [generator-filter-map (-> procedure? procedure? procedure?)] [generator-remove-duplicates (opt-> (procedure?) (procedure?) procedure?)] [generator-debug (-> string? procedure? procedure?)] [generator-for-each (->* (procedure?) (listof procedure?) any)] [generator-accumulate (->* (procedure? any/c) (listof procedure?) any)] [generator->list (-> procedure? (or/c pair? null?))] [list->generator (-> (or/c pair? null?) procedure?)] [generator-project (opt-> ((listof boolean?) procedure?) (procedure?) procedure?)]) )