Generator Utilities
(require generator-util) | package: generator-util |
Primitives and utilities for working with generators.
This module provides general-purpose utilities to achieve standard "list-like" transformations with generators without losing the laziness and constant-memory guarantees that generators provide. Alternative bindings for many of those found in racket/generator are included here, so that importing both is generally not necessary.
This module additionally provides a generic interface gen:generator representing a generator, as well as a particular generator type gen implementing that interface. The former allows supporting generator semantics in custom types and the latter is a rich generator type usable as a drop-in replacement for built-in generators, which comes with some conveniences.
Caveat: These utilities are not suitable for use with coroutines, i.e. in cases where there is bidirectional communication with a generator. This is because the utilities wrap underlying generators with intermediary ones in some cases, and values sent to them are not conveyed to the underlying generators.
1 Constructors
struct
primitive : generator?
> (define g1 (: 1 2 (generator-null))) > (define g2 (: 3 4 5 g1)) > (g2) 3
> (g2) 4
> (g2) 5
> (g2) 1
> (g2) 2
procedure
(generator-null [return]) → generator?
return : any/c = (void)
procedure
(generator-cons v g) → generator?
v : any/c g : generator?
procedure
(make-generator v ...) → generator?
v : any/c
Note that these constructors are not lazy. In order to construct a generator from a sequence lazily, use generate instead.
These constructors could either be used directly or via the generic construction operator : – see gen for examples of this.
> (define g (generator-cons 1 (generator-null))) > (g) 1
> (void? (g)) #t
> (define g (generator-cons 1 (generator-null 23))) > (g) 1
> (g) 23
> (define g (make-generator 1 2 3)) > (g) 1
> (->list (in-producer g (void))) '(2 3)
2 Syntax
syntax
(generator formals body ...)
3 Predicates
procedure
(generator-done? g) → boolean?
g : generator?
procedure
(generator-empty? g) →
boolean? generator? g : generator?
generator-empty? returns both a boolean value indicating whether the generator is empty or not, as well as a fresh generator intended to supplant the original generator in the calling context. This is necessary because checking for emptiness requires invoking the generator to inspect the first element, which mutates the original generator. The returned generator is equivalent to the original generator prior to the mutation, modulo the aforementioned caveat about coroutines.
In general, favor using generator-done?.
> (define g (make-generator)) > (generator-done? g) #f
> (define-values (is-empty? g) (generator-empty? g)) > is-empty? #t
> (generator-done? g) #f
> (g) > (generator-done? g) #t
procedure
(generator-peek g) →
any/c generator? g : generator?
> (define g (make-generator 1 2 3)) > (define-values (v g) (generator-peek g)) > v 1
> (g) 1
> (g) 2
> (g) 3
4 Utilities
procedure
(generator-map f g) → generator?
f : (-> any/c any/c) g : generator?
> (define g (make-generator 1 2 3)) > (define g (generator-map add1 g)) > (g) 2
> (g) 3
> (g) 4
procedure
(generator-filter f g) → generator?
f : (-> any/c boolean?) g : generator?
> (define g (make-generator 1 2 3 4 5)) > (define g (generator-filter odd? g)) > (g) 1
> (g) 3
> (g) 5
procedure
(generator-fold f g [base #:order order]) → generator?
f : procedure? g : generator? base : any/c = undefined order : (one-of/c 'abb 'bab) = 'abb
> (define g (make-generator 1 2 3 4)) > (define g (generator-fold + g)) > (g) 1
> (g) 3
> (g) 6
> (g) 10
procedure
(generator-append a b) → generator?
a : generator? b : generator?
> (define a (make-generator 1 2)) > (define b (make-generator 3 4)) > (define g (generator-append a b)) > (g) 1
> (g) 2
> (g) 3
> (g) 4
procedure
(generator-cycle g [stop]) → generator?
g : generator? stop : any/c = (void)
> (define g (generator-cycle (make-generator 1 2 3))) > (g) 1
> (g) 2
> (g) 3
> (g) 1
> (g) 2
procedure
(generator-repeat v) → generator?
v : any/c
> (define g (generator-repeat 5)) > (g) 5
> (g) 5
> (g) 5
procedure
(generator-zip g ...) → generator?
g : generator?
procedure
(generator-zip-with f g ...) → generator?
f : procedure? g : generator?
> (define a (make-generator 1 2)) > (define b (make-generator 'a 'b)) > (define c (make-generator 'A 'B)) > (define g (generator-zip a b c)) > (g) '(1 a A)
> (g) '(2 b B)
> (define a (make-generator 1 2)) > (define b (make-generator 1 2)) > (define c (make-generator 1 2)) > (define g (generator-zip-with + a b c)) > (g) 3
> (g) 6
procedure
(generator-interleave g ...) → generator?
g : generator?
> (define g (generator-interleave (make-generator 1 2 3) (make-generator 4 5 6))) > (g) 1
> (g) 4
> (g) 2
> (g) 5
> (g) 3
> (g) 6
procedure
(generator-join g) → generator?
g : generator?
> (define g (make-generator (list 1) (list 2) (list 3))) > (define g (generator-join g)) > (g) 1
> (g) 2
> (g) 3
procedure
(generator-flatten g) → generator?
g : generator?
> (define g (make-generator (list (list (list 1))) (list (list (list 2))) (list (list (list 3))))) > (define g (generator-flatten g)) > (g) 1
> (g) 2
> (g) 3
procedure
(yield-from g) → any
g : generator?
> (define g (generator () (yield-from (make-generator 1 2 3)))) > (g) 1
> (g) 2
> (g) 3
5 Transformers
procedure
(generate seq [return]) → generator?
seq : sequence? return : any/c = (void)
To go in the "other direction" and transform a generator into a sequence, use in-producer.
> (define g (generate (range 5 10))) > (g) 5
> (g) 6
> (g) 7
> (define g (generate "apple")) > (g) #\a
> (g) #\p
> (g) #\p
procedure
(in-producer g [stop] v ...) → sequence?
g : generator? stop : any/c = undefined v : any/c
To go in the "other direction" and transform a sequence into a generator, use generate.
> (define g (make-generator 1 2 3)) > (->list (in-producer g (void))) '(1 2 3)
6 Interface
value
> (struct api-reader (source) #:transparent #:property prop:procedure (λ (self) ((api-reader-source self))) #:methods gen:generator [(define/generic -generator-state generator-state) (define (generator-state st) (-generator-state (api-reader-source source)))]) > (define g (api-reader (make-generator 1 2 3))) > (g) 1
> (->list (in-producer g (void))) '(2 3)
To implement this interface for custom types, the following method needs to be implemented:
procedure
(generator-state v)
→ [symbol? (one-of/c 'fresh 'suspended 'running 'done)] v : generator?
procedure
(generator? v) → boolean?
v : any/c
> (generator? 3) #f
> (generator? (generator () (void))) #t
> (generator? (generator-cons 1 (generator-null))) #t