racket/collects/scribblings/reference/sequences.scrbl
2010-01-11 22:22:51 +00:00

406 lines
15 KiB
Racket

#lang scribble/doc
@(require "mz.ss"
(for-syntax scheme/base)
scribble/scheme
(for-label scheme/generator))
@(define generator-eval
(lambda ()
(let ([the-eval (make-base-eval)])
(the-eval '(require scheme/generator))
the-eval)))
@(define (info-on-seq where what)
@margin-note{See @secref[where] for information on using @|what| as sequences.})
@title[#:tag "sequences"]{Sequences}
@guideintro["sequences"]{sequences}
A @deftech{sequence} encapsulates an ordered stream of values. The
elements of a sequence can be extracted with one of the @scheme[for]
syntactic forms or with the procedures returned by
@scheme[sequence-generate].
The sequence datatype overlaps with many other datatypes. Among
built-in datatypes, the sequence datatype includes the following:
@itemize[
@item{lists (see @secref["pairs"])}
@item{vectors (see @secref["vectors"])}
@item{hash tables (see @secref["hashtables"])}
@item{strings (see @secref["strings"])}
@item{byte strings (see @secref["bytestrings"])}
@item{input ports (see @secref["ports"])}
]
In addition, @scheme[make-do-sequence] creates a sequence given a
thunk that returns procedures to implement a generator, and the
@scheme[prop:sequence] property can be associated with a structure
type.
For most sequence types, extracting elements from a sequence has no
side-effect on the original sequence value; for example, extracting the
sequence of elements from a list does not change the list. For other
sequence types, each extraction implies a side effect; for example,
extracting the sequence of bytes from a port cause the bytes to be
read from the port.
Inidvidual elements of a sequence typically correspond to single
values, but an element may also correspond to multiple values. For
example, a hash table generates two values---a key and its value---for
each element in the sequence.
@section{Sequence Predicate and Constructors}
@defproc[(sequence? [v any/c]) boolean?]{ Return @scheme[#t] if
@scheme[v] can be used as a sequence, @scheme[#f] otherwise.}
@defproc*[([(in-range [end number?]) sequence?]
[(in-range [start number?] [end number?] [step number? 1]) sequence?])]{
Returns a sequence whose elements are numbers. The single-argument
case @scheme[(in-range end)] is equivalent to @scheme[(in-range 0 end
1)]. The first number in the sequence is @scheme[start], and each
successive element is generated by adding @scheme[step] to the
previous element. The sequence stops before an element that would be
greater or equal to @scheme[end] if @scheme[step] is non-negative, or
less or equal to @scheme[end] if @scheme[step] is negative.
@speed[in-range "number"]}
@defproc[(in-naturals [start exact-nonnegative-integer? 0]) sequence?]{
Returns an infinite sequence of exact integers starting with
@scheme[start], where each element is one more than the preceeding
element. @speed[in-naturals "integer"]}
@defproc[(in-list [lst list?]) sequence?]{
Returns a sequence equivalent to @scheme[lst].
@info-on-seq["pairs" "lists"]
@speed[in-list "list"]}
@defproc[(in-vector [vec vector?]
[start exact-nonnegative-integer? 0]
[stop (or/c exact-nonnegative-integer? #f) #f]
[step (and/c exact-integer? (not/c zero?)) 1])
sequence?]{
Returns a sequence equivalent to @scheme[vec] when no optional
arguments are supplied.
@info-on-seq["vectors" "vectors"]
The optional arguments @scheme[start], @scheme[stop], and
@scheme[step] are analogous to @scheme[in-range], except that a
@scheme[#f] value for @scheme[stop] is equivalent to
@scheme[(vector-length vec)]. That is, the first element in the
sequence is @scheme[(vector-ref vec start)], and each successive
element is generated by adding @scheme[step] to index of the previous
element. The sequence stops before an index that would be greater or
equal to @scheme[end] if @scheme[step] is non-negative, or less or
equal to @scheme[end] if @scheme[step] is negative.
If @scheme[start] is less than @scheme[stop] and @scheme[step] is
negative, then the @exnraise[exn:fail:contract:mismatch]. Similarly,
if @scheme[start] is more than @scheme[stop] and @scheme[step] is
positive, then the @exnraise[exn:fail:contract:mismatch]. The
@scheme[start] and @scheme[stop] values are @emph{not} checked against
the size of @scheme[vec], so access can fail when an element is
demanded from the sequence.
@speed[in-vector "vector"]}
@defproc[(in-string [str string?]
[start exact-nonnegative-integer? 0]
[stop (or/c exact-nonnegative-integer? #f) #f]
[step (and/c exact-integer? (not/c zero?)) 1])
sequence?]{
Returns a sequence equivalent to @scheme[str] when no optional
arguments are supplied.
@info-on-seq["strings" "strings"]
The optional arguments @scheme[start], @scheme[stop], and
@scheme[step] are as in @scheme[in-vector].
@speed[in-string "string"]}
@defproc[(in-bytes [bstr bytes?]
[start exact-nonnegative-integer? 0]
[stop (or/c exact-nonnegative-integer? #f) #f]
[step (and/c exact-integer? (not/c zero?)) 1])
sequence?]{
Returns a sequence equivalent to @scheme[bstr] when no optional
arguments are supplied.
@info-on-seq["bytestrings" "byte strings"]
The optional arguments @scheme[start], @scheme[stop], and
@scheme[step] are as in @scheme[in-vector].
@speed[in-bytes "byte string"]}
@defproc[(in-port [r (input-port? . -> . any/c) read]
[in input-port? (current-input-port)])
sequence?]{
Returns a sequence whose elements are produced by calling @scheme[r]
on @scheme[in] until it produces @scheme[eof].}
@defproc[(in-input-port-bytes [in input-port?]) sequence?]{
Returns a sequence equivalent to @scheme[(in-port read-byte in)].}
@defproc[(in-input-port-chars [in input-port?]) sequence?]{ Returns a
sequence whose elements are read as characters form @scheme[in]
(equivalent to @scheme[(in-port read-char in)]).}
@defproc[(in-lines [in input-port? (current-input-port)]
[mode (or/c 'linefeed 'return 'return-linefeed 'any 'any-one) 'any])
sequence?]{
Returns a sequence equivalent to @scheme[(in-port (lambda (p)
(read-line p mode)) in)]. Note that the default mode is @scheme['any],
whereas the default mode of @scheme[read-line] is
@scheme['linefeed]. }
@defproc[(in-bytes-lines [in input-port? (current-input-port)]
[mode (or/c 'linefeed 'return 'return-linefeed 'any 'any-one) 'any])
sequence?]{
Returns a sequence equivalent to @scheme[(in-port (lambda (p)
(read-bytes-line p mode)) in)]. Note that the default mode is @scheme['any],
whereas the default mode of @scheme[read-bytes-line] is
@scheme['linefeed]. }
@defproc[(in-hash [hash hash?]) sequence?]{
Returns a sequence equivalent to @scheme[hash].
@info-on-seq["hashtables" "hash tables"]}
@defproc[(in-hash-keys [hash hash?]) sequence?]{
Returns a sequence whose elements are the keys of @scheme[hash].}
@defproc[(in-hash-values [hash hash?]) sequence?]{
Returns a sequence whose elements are the values of @scheme[hash].}
@defproc[(in-hash-pairs [hash hash?]) sequence?]{
Returns a sequence whose elements are pairs, each containing a key and
its value from @scheme[hash] (as opposed to using @scheme[hash] directly
as a sequence to get the key and value as separate values for each
element).}
@defproc[(in-producer [producer procedure?] [stop any/c] [args any/c] ...)
sequence?]{
Returns a sequence that contains values from sequential calls to
@scheme[producer]. @scheme[stop] identifies the value that marks the
end of the sequence --- this value is not included in the sequence.
@scheme[stop] can be a predicate or a value that is tested against the
results with @scheme[eq?]. Note that you must use a predicate function
if the stop value is itself a function, or if the @scheme[producer]
returns multiple values.}
@defproc[(in-value [v any/c]) sequence?]{
Returns a sequence that produces a single value: @scheme[v]. This form
is mostly useful for @scheme[let]-like bindings in forms such as
@scheme[for*/list].}
@defproc[(in-indexed [seq sequence?]) sequence?]{Returns a sequence
where each element has two values: the value produced by @scheme[seq],
and a non-negative exact integer starting with @scheme[0]. The
elements of @scheme[seq] must be single-valued.}
@defproc[(in-sequences [seq sequence?] ...) sequence?]{Returns a
sequence that is made of all input sequences, one after the other. The
elements of each @scheme[seq] must all have the same number of
values.}
@defproc[(in-cycle [seq sequence?] ...) sequence?]{Similar to
@scheme[in-sequences], but the sequences are repeated in an infinite
cycle.}
@defproc[(in-parallel [seq sequence?] ...) sequence?]{Returns a
sequence where each element has as many values as the number of
supplied @scheme[seq]s; the values, in order, are the values of each
@scheme[seq]. The elements of each @scheme[seq] must be
single-valued.}
@defproc[(stop-before [seq sequence?] [pred (any/c . -> . any)])
sequence?]{ Returns a sequence that contains the elements of
@scheme[seq] (which must be single-valued), but only until the last
element for which applying @scheme[pred] to the element produces
@scheme[#t], after which the sequence ends.}
@defproc[(stop-after [seq sequence?] [pred (any/c . -> . any)])
sequence?]{ Returns a sequence that contains the elements of
@scheme[seq] (which must be single-valued), but only until the element
(inclusive) for which applying @scheme[pred] to the element produces
@scheme[#t], after which the sequence ends.}
@defproc[(make-do-sequence [thunk (-> (values (any/c . -> . any)
(any/c . -> . any/c)
any/c
(any/c . -> . any/c)
(() () #:rest list? . ->* . any/c)
((any/c) () #:rest list? . ->* . any/c)))])
sequence?]{
Returns a sequence whose elements are generated by the procedures and
initial value returned by the thunk. The generator is defined in terms
of a @defterm{position}, which is initialized to the third result of
the thunk, and the @defterm{element}, which may consist of multiple
values.
The @scheme[thunk] results define the generated elements as follows:
@itemize[
@item{The first result is a @scheme[_pos->element] procedure that takes
the current position and returns the value(s) for the current element.}
@item{The second result is a @scheme[_next-pos] procedure that takes
the current position and returns the next position.}
@item{The third result is the initial position.}
@item{The fourth result takes the current position and returns a true
result if the sequence includes the value(s) for the current
position, and false if the sequence should end instead of
including the value(s).}
@item{The fifth result is like the fourth result, but it takes the
current element value(s) instead of the current position.}
@item{The sixth result is like the fourth result, but it takes both
the current position and the current element values(s) and
determines a sequence end after the current element is already
included in the sequence.}
]
Each of the procedures listed above is called only once per position.
Among the last three procedures, as soon as one of the procedures
returns @scheme[#f], the sequence ends, and none are called
again. Typically, one of the functions determines the end condition,
and the other two functions always return @scheme[#t].}
@defthing[prop:sequence struct-type-property?]{
Associates a procedure to a structure type that takes an instance of
the structure and returns a sequence. If @scheme[v] is an instance of
a structure type with this property, then @scheme[(sequence? v)]
produces @scheme[#t].
@let-syntax[([car (make-element-id-transformer (lambda (id) #'@schemeidfont{car}))])
@examples[
(define-struct train (car next)
#:property prop:sequence (lambda (t)
(make-do-sequence
(lambda ()
(values train-car
train-next
t
(lambda (t) t)
(lambda (v) #t)
(lambda (t v) #t))))))
(for/list ([c (make-train 'engine
(make-train 'boxcar
(make-train 'caboose
#f)))])
c)
]]}
@section{Sequence Generators}
@defproc[(sequence-generate [seq sequence?]) (values (-> boolean?)
(-> any))]{
Returns two thunks to extract elements from the sequence. The first
returns @scheme[#t] if more values are available for the sequence. The
second returns the next element (which may be multiple values) from the
sequence; if no more elements are available, the
@exnraise[exn:fail:contract].}
@section{Iterator Generators}
@defmodule[scheme/generator]
@defform[(generator body ...)]{ Creates a function that returns a
value, usually through @scheme[yield], each time it is invoked. When
the generator runs out of values to yield the last value it computed
will be returned for future invocations of the generator. Generators
can be safely nested.
@examples[#:eval (generator-eval)
(define g (generator
(let loop ([x '(a b c)])
(if (null? x)
0
(begin
(yield (car x))
(loop (cdr x)))))))
(g)
(g)
(g)
(g)
(g)
]
To use an existing generator as a sequence, you should use @scheme[in-producer]
with a stop-value known to the generator.
@examples[#:eval (generator-eval)
(define my-stop-value (gensym))
(define my-generator (generator
(let loop ([x '(a b c)])
(if (null? x)
my-stop-value
(begin
(yield (car x))
(loop (cdr x)))))))
(for/list ([i (in-producer my-generator my-stop-value)])
i)
]}
@defform[(infinite-generator body ...)]{ Creates a function similar to
@scheme[generator] but when the last @scheme[body] is executed the function
will re-execute all the bodies in a loop.
@examples[#:eval (generator-eval)
(define welcome
(infinite-generator
(yield 'hello)
(yield 'goodbye)))
(welcome)
(welcome)
(welcome)
(welcome)
]}
@defproc[(in-generator [expr any?] ...) sequence?]{ Returns a generator
that can be used as a sequence. The @scheme[in-generator] procedure takes care of the
case when @scheme[expr] stops producing values, so when the @scheme[expr]
completes, the generator will end.
@examples[#:eval (generator-eval)
(for/list ([i (in-generator
(let loop ([x '(a b c)])
(when (not (null? x))
(yield (car x))
(loop (cdr x)))))])
i)
]}
@defform[(yield expr)]{ Saves the point of execution inside a generator
and returns a value.}
@defproc[(sequence->generator [s sequence?]) (-> any?)]{ Returns a generator
that returns elements from the sequence, @scheme[s], each time the generator
is invoked.}
@defproc[(sequence->repeated-generator [s sequence?]) (-> any?)]{ Returns a generator
that returns elements from the sequence, @scheme[s], similar to
@scheme[sequence->generator] but looping over the values in the sequence
when no more values are left.}