dictionaries

svn: r9383
This commit is contained in:
Matthew Flatt 2008-04-21 17:26:35 +00:00
parent ed478254e2
commit a0f65ba33e
21 changed files with 2508 additions and 1348 deletions

View File

@ -946,6 +946,8 @@ operations that expect textual ports.
(hashtable-clear! #f "r6rs-lib-Z-H-14.html" "node_idx_1204")
(equal-hash #f "r6rs-lib-Z-H-14.html" "node_idx_1218")]
A hashtable is a dictionary in the sense of @schememodname[scheme/dict].
@r6rs-module[rnrs/enums-6 (rnrs enums (6))
"r6rs-lib-Z-H-15.html" "node_idx_1226" "Enumerations"
(make-enumeration #f "r6rs-lib-Z-H-15.html" "node_idx_1236")

View File

@ -1,5 +1,7 @@
#lang scheme/base
(require scheme/dict)
(provide make-eq-hashtable
make-eqv-hashtable
(rename-out [r6rs:make-hashtable make-hashtable])
@ -27,7 +29,23 @@
unwrap
mutable?
equivalence-function
hash-function))
hash-function)
#:property prop:dict
(vector (case-lambda
[(ht key)
(hash-ref (hashtable-ht ht) ((hashtable-wrap ht) key))]
[(ht key default)
(hash-ref (hashtable-ht ht) ((hashtable-wrap ht) key) default)])
(lambda (ht key val) (hashtable-set! ht key val))
#f
(lambda (ht key) (hashtable-delete! ht key))
#f
(lambda (ht) (hashtable-size ht))
(lambda (ht) (hash-iterate-first (hashtable-ht ht)))
(lambda (ht pos) (hash-iterate-next (hashtable-ht ht) pos))
(lambda (ht pos) ((hashtable-unwrap ht)
(hash-iterate-key (hashtable-ht ht) pos)))
(lambda (ht pos) (hash-iterate-value (hashtable-ht ht) pos))))
(define-struct eqv-box (val)
#:property prop:equal+hash (list

517
collects/scheme/dict.ss Normal file
View File

@ -0,0 +1,517 @@
#lang scheme/base
(require (for-syntax scheme/base))
(provide prop:dict
dict?
dict-mutable?
dict-can-remove-keys?
dict-can-functional-set?
dict-ref
dict-set!
dict-set
dict-remove!
dict-remove
dict-count
dict-iterate-first
dict-iterate-next
dict-iterate-key
dict-iterate-value
dict-map
dict-for-each
in-dict
in-dict-keys
in-dict-values
in-dict-pairs
(rename-out [create-custom-hash make-custom-hash]
[create-immutable-custom-hash make-immutable-custom-hash])
make-weak-custom-hash)
(define-values (prop:dict dict-struct? dict-struct-ref)
(make-struct-type-property 'dict
(lambda (v info)
(unless (and
(vector? v)
(= 10 (vector-length v))
(let-values ([(ref set! set remove! remove count
iterate-first iterate-next
iterate-key iterate-value)
(vector->values v)])
(and (procedure? ref)
(and (procedure-arity-includes? ref 2)
(procedure-arity-includes? ref 3))
(or (not set!)
(and (procedure? set!)
(procedure-arity-includes? set! 3)))
(or (not set)
(and (procedure? set)
(procedure-arity-includes? set 3)))
(or (not remove!)
(and (procedure? remove!)
(procedure-arity-includes? remove! 2)))
(or (not remove)
(and (procedure? remove)
(procedure-arity-includes? remove 2)))
(procedure? count)
(procedure-arity-includes? count 1)
(procedure? iterate-first)
(procedure-arity-includes? iterate-first 1)
(procedure? iterate-next)
(procedure-arity-includes? iterate-next 2)
(procedure? iterate-key)
(procedure-arity-includes? iterate-key 2)
(procedure? iterate-value)
(procedure-arity-includes? iterate-value 2))))
(raise-type-error 'prop:dict-guard
"vector of dict methods"
v))
v)))
(define (get-dict-ref v)
(vector-ref v 0))
(define (get-dict-set! v)
(vector-ref v 1))
(define (get-dict-set v)
(vector-ref v 2))
(define (get-dict-remove! v)
(vector-ref v 3))
(define (get-dict-remove v)
(vector-ref v 4))
(define (get-dict-count v)
(vector-ref v 5))
(define (get-dict-iterate-first v)
(vector-ref v 6))
(define (get-dict-iterate-next v)
(vector-ref v 7))
(define (get-dict-iterate-key v)
(vector-ref v 8))
(define (get-dict-iterate-value v)
(vector-ref v 9))
(define (assoc? v)
(and (list? v) (andmap pair? v)))
(define (dict? v)
(or (hash? v)
(vector? v)
(assoc? v)
(dict-struct? v)))
(define (dict-mutable? d)
(if (dict? d)
(or (and (or (hash? d)
(vector? d))
(not (immutable? d)))
(and (dict-struct? d)
(get-dict-set! (dict-struct-ref d))
#t))
(raise-type-error 'dict-mutable? "dict" d)))
(define (dict-can-remove-keys? d)
(if (dict? d)
(or (hash? d)
(assoc? d)
(and (dict-struct? d)
(or (get-dict-remove! (dict-struct-ref d))
(get-dict-remove (dict-struct-ref d)))
#t))
(raise-type-error 'dict-can-remove-keys? "dict" d)))
(define (dict-can-functional-set? d)
(if (dict? d)
(or (and (hash? d) (immutable? d))
(assoc? d)
(and (dict-struct? d)
(get-dict-set (dict-struct-ref d))
#t))
(raise-type-error 'dict-can-functional-set? "dict" d)))
(define dict-ref
(case-lambda
[(d key)
(cond
[(hash? d) (hash-ref d key)]
[(vector? d) (vector-ref d key)]
[(assoc? d)
(let ([a (assoc key d)])
(if a
(cdr a)
(raise-mismatch-error 'dict-ref
(format "no value for key: ~e in: "
key)
d)))]
[(dict-struct? d)
((get-dict-ref (dict-struct-ref d)) d key)]
[else
(raise-type-error 'dict-ref 'dict 0 d key)])]
[(d key default)
(cond
[(hash? d) (hash-ref d key default)]
[(vector? d) (if (and (exact-nonnegative-integer? key)
(key . < . (vector-length d)))
(vector-ref d key)
(if (procedure? default)
(default)
default))]
[(assoc? d)
(let ([a (assoc key d)])
(if a
(cdr a)
(if (procedure? default)
(default)
default)))]
[(dict-struct? d)
((get-dict-ref (dict-struct-ref d)) d key default)]
[else
(raise-type-error 'dict-ref 'dict 0 d key default)])]))
(define (dict-set! d key val)
(cond
[(hash? d) (hash-set! d key val)]
[(vector? d) (vector-set! d key val)]
[(assoc? d)
(raise-type-error 'dict-set! "mutable dict" 0 d key val)]
[(dict-struct? d)
(let ([s! (get-dict-set! (dict-struct-ref d))])
(if s!
(s! d key val)
(raise-type-error 'dict-set! "mutable dict" 0 d key val)))]
[else
(raise-type-error 'dict-set! "dict" 0 d key val)]))
(define (dict-set d key val)
(cond
[(hash? d) (hash-set d key val)]
[(vector? d)
(raise-type-error 'dict-set "functional-update dict" 0 d key val)]
[(assoc? d)
(let loop ([xd d])
(cond
[(null? xd) (list (cons key val))]
[else
(let ([a (car xd)])
(if (equal? (car a) key)
(cons (cons key val) (cdr xd))
(cons a (loop (cdr xd)))))]))]
[(dict-struct? d)
(let ([s (get-dict-set (dict-struct-ref d))])
(if s
(s d key val)
(raise-type-error 'dict-set "functional-update dict" 0 d key val)))]
[else
(raise-type-error 'dict-set "dict" 0 d key val)]))
(define (dict-remove! d key)
(cond
[(hash? d) (hash-remove! d key)]
[(vector? d)
(raise-type-error 'dict-remove! "dict with removeable keys" 0 d key)]
[(assoc? d)
(raise-type-error 'dict-remove! "mutable dict" 0 d key)]
[(dict-struct? d)
(let ([r! (get-dict-remove! (dict-struct-ref d))])
(if r!
(r! d key)
(raise-type-error 'dict-remove! "mutable dict with removable keys" 0 d key)))]
[else
(raise-type-error 'dict-remove! "dict" 0 d key)]))
(define (dict-remove d key)
(cond
[(hash? d) (hash-remove d key)]
[(vector? d)
(raise-type-error 'dict-remove "dict with removeable keys" 0 d key)]
[(assoc? d)
(let loop ([xd d])
(cond
[(null? xd) null]
[else
(let ([a (car xd)])
(if (equal? (car a) key)
(cdr xd)
(cons a (loop (cdr xd)))))]))]
[(dict-struct? d)
(let ([s (get-dict-remove (dict-struct-ref d))])
(if s
(s d key)
(raise-type-error 'dict-remove "dict with functionally removeable keys" 0 d key)))]
[else
(raise-type-error 'dict-remove "dict" 0 d key)]))
(define (dict-count d)
(cond
[(hash? d) (hash-count d)]
[(vector? d) (vector-length d)]
[(assoc? d) (length d)]
[(dict-struct? d) ((get-dict-count (dict-struct-ref d)) d)]
[else
(raise-type-error 'dict-count "dict" d)]))
(define-struct assoc-iter (head pos))
(define (dict-iterate-first d)
(cond
[(hash? d) (hash-iterate-first d)]
[(vector? d) (if (zero? (vector-length d))
#f
0)]
[(assoc? d) (if (null? d) #f (make-assoc-iter d d))]
[(dict-struct? d) ((get-dict-iterate-first (dict-struct-ref d)) d)]
[else
(raise-type-error 'dict-iterate-first "dict" d)]))
(define (dict-iterate-next d i)
(cond
[(hash? d) (hash-iterate-next d i)]
[(vector? d) (let ([len (vector-length d)])
(cond
[(and (exact-nonnegative-integer? i)
(i . < . len))
(let ([i (add1 i)])
(if (= i len)
#f
i))]
[else
(raise-mismatch-error
'dict-iterate-next
"invalid iteration position for vector: "
i)]))]
[(and (assoc-iter? i)
(eq? d (assoc-iter-head i)))
(let ([pos (cdr (assoc-iter-pos i))])
(if (null? pos)
#f
(make-assoc-iter d pos)))]
[(dict-struct? d) ((get-dict-iterate-next (dict-struct-ref d)) d i)]
[(assoc? d)
(raise-mismatch-error
'dict-iterate-next
"invalid iteration position for association list: "
i)]
[else
(raise-type-error 'dict-iterate-next "dict" d)]))
(define (dict-iterate-key d i)
(cond
[(hash? d) (hash-iterate-key d i)]
[(vector? d) i]
[(and (assoc-iter? i) (eq? d (assoc-iter-head i))) (caar (assoc-iter-pos i))]
[(dict-struct? d) ((get-dict-iterate-key (dict-struct-ref d)) d i)]
[(assoc? d)
(raise-mismatch-error
'dict-iterate-key
"invalid iteration position for association list: "
i)]
[else
(raise-type-error 'dict-iterate-key "dict" d)]))
(define (dict-iterate-value d i)
(cond
[(hash? d) (hash-iterate-value d i)]
[(vector? d) (vector-ref d i)]
[(and (assoc-iter? i) (eq? d (assoc-iter-head i))) (cdar (assoc-iter-pos i))]
[(dict-struct? d) ((get-dict-iterate-value (dict-struct-ref d)) d i)]
[(assoc? d)
(raise-mismatch-error
'dict-iterate-value
"invalid iteration position for association list: "
i)]
[else
(raise-type-error 'dict-iterate-value "dict" d)]))
(define-sequence-syntax :in-dict
(lambda () #'in-dict)
(lambda (stx)
(syntax-case stx ()
[((key-id val-id) (_ dict-expr))
#'[(key-id val-id)
(:do-in ([(d) dict-expr])
(void)
([i (dict-iterate-first d)])
i
([key-id (dict-iterate-key d i)]
[val-id (dict-iterate-value d i)])
#t
#t
((dict-iterate-next d i)))]]
[_ #f])))
(define (in-dict d)
(make-dict-sequence
d
(lambda (i)
(values (dict-iterate-key d i)
(dict-iterate-value d i)))
(lambda (k v) #t)
(lambda (i k v) #t)))
(define (in-dict-keys d)
(make-dict-sequence
d
(lambda (i) (dict-iterate-key d i))
(lambda (k) #t)
(lambda (i k) #t)))
(define (in-dict-values d)
(make-dict-sequence
d
(lambda (i) (dict-iterate-value d i))
(lambda (v) #t)
(lambda (i v) #t)))
(define (in-dict-pairs d)
(make-dict-sequence
d
(lambda (i)
(cons (dict-iterate-key d i)
(dict-iterate-value d i)))
(lambda (p) #t)
(lambda (i p) #t)))
(define (make-dict-sequence d get val-true val+pos-true)
(make-do-sequence
(lambda ()
(values get
(lambda (i) (dict-iterate-next d i))
(dict-iterate-first d)
(lambda (i) i)
val-true
val+pos-true))))
(define (dict-map d f)
(for/list ([(k v) (in-dict d)])
(f k v)))
(define (dict-for-each d f)
(for ([(k v) (in-dict d)])
(f k v)))
;; ----------------------------------------
(define-struct hash-box (key))
(define custom-hash-ref
(case-lambda
[(d k) (hash-ref (custom-hash-table d)
((custom-hash-make-box d) k)
(lambda ()
(raise-mismatch-error
'dict-ref
"no value found for key: "
k)))]
[(d k fail) (hash-ref (custom-hash-table d)
((custom-hash-make-box d) k)
fail)]))
(define (custom-hash-set! d k v)
(hash-set! (custom-hash-table d)
((custom-hash-make-box d) k)
v))
(define (custom-hash-set d k v)
(let ([table (hash-set (custom-hash-table d)
((custom-hash-make-box d) k)
v)])
(make-immutable-custom-hash table
(custom-hash-make-box d))))
(define (custom-hash-remove! d k)
(hash-remove! (custom-hash-table d)
((custom-hash-make-box d) k)))
(define (custom-hash-remove d k)
(let ([table (hash-remove (custom-hash-table d)
((custom-hash-make-box d) k))])
(make-immutable-custom-hash table
(custom-hash-make-box d))))
(define (custom-hash-count d)
(hash-count (custom-hash-table d)))
(define (custom-hash-iterate-first d)
(hash-iterate-first (custom-hash-table d)))
(define (custom-hash-iterate-next d i)
(hash-iterate-next (custom-hash-table d) i))
(define (custom-hash-iterate-key d i)
(hash-box-key (hash-iterate-key (custom-hash-table d) i)))
(define (custom-hash-iterate-value d i)
(hash-iterate-value (custom-hash-table d) i))
(define-struct custom-hash (table make-box)
#:property prop:dict
(vector custom-hash-ref
custom-hash-set!
#f
custom-hash-remove!
#f
custom-hash-count
custom-hash-iterate-first
custom-hash-iterate-next
custom-hash-iterate-key
custom-hash-iterate-value)
#:property prop:equal+hash
(list (lambda (a b recur)
(and (recur (custom-hash-make-box a)
(custom-hash-make-box b))
(recur (custom-hash-table a)
(custom-hash-table b))))
(lambda (a recur) (recur (custom-hash-table a)))
(lambda (a recur) (recur (custom-hash-table a)))))
(define-struct (immutable-custom-hash custom-hash) ()
#:property prop:dict
(vector custom-hash-ref
#f
custom-hash-set
#f
custom-hash-remove
custom-hash-count
custom-hash-iterate-first
custom-hash-iterate-next
custom-hash-iterate-key
custom-hash-iterate-value))
(define-values (create-custom-hash
create-immutable-custom-hash
make-weak-custom-hash)
(let ([mk
(lambda (hash hash2 =? who make-custom-hash table)
(unless (and (procedure? =?)
(procedure-arity-includes? =? 2))
(raise-type-error who "procedure (arity 2)" =?))
(unless (and (procedure? hash)
(procedure-arity-includes? hash 1))
(raise-type-error who "procedure (arity 1)" hash))
(unless (and (procedure? hash2)
(procedure-arity-includes? hash2 1))
(raise-type-error who "procedure (arity 1)" hash2))
(let ()
(define-struct (box hash-box) ()
#:property prop:equal+hash (list
(lambda (a b recur)
(=? (hash-box-key a) (hash-box-key b)))
(lambda (v recur)
(hash (hash-box-key v)))
(lambda (v recur)
(hash2 (hash-box-key v)))))
(make-custom-hash table make-box)))])
(let ([make-custom-hash
(lambda (=? hash [hash2 (lambda (v) 10001)])
(mk hash hash2 =? 'make-custom-hash make-custom-hash (make-hash)))]
[make-immutable-custom-hash
(lambda (=? hash [hash2 (lambda (v) 10001)])
(mk hash hash2 =? 'make-immutable-custom-hash make-immutable-custom-hash #hash()))]
[make-weak-custom-hash
(lambda (=? hash [hash2 (lambda (v) 10001)])
(mk hash hash2 =? 'make-immutable-custom-hash make-immutable-custom-hash (make-weak-hash)))])
(values make-custom-hash
make-immutable-custom-hash
make-weak-custom-hash))))

View File

@ -2,6 +2,7 @@
(require scheme/contract
scheme/class
scheme/unit
scheme/dict
scheme/include
scheme/pretty
scheme/math
@ -24,6 +25,7 @@
(provide (all-from-out scheme/contract
scheme/class
scheme/unit
scheme/dict
scheme/include
scheme/pretty
scheme/math

View File

@ -26,7 +26,7 @@
(define-sequence-syntax in-par
(lambda () (raise-syntax-error 'in-par "bad"))
(lambda (orig-stx stx)
(lambda (stx)
(syntax-case stx ()
[((id) (_ lst-exprs))
#'[(id)

View File

@ -74,7 +74,7 @@
0
proc1 proc2))
(unless (and (procedure? proc2)
(procedure-arity-includes? proc2 2))
(procedure-arity-includes? proc2 1))
(raise-type-error 'define-sequence-syntax
"procedure (arity 1)"
1
@ -165,7 +165,7 @@
(let ([xformer (sequence-transformer-ref m 1)]
[introducer (make-syntax-introducer)]
[certifier (sequence-transformer-ref m 2)])
(let ([xformed (xformer orig-stx (introducer (syntax-local-introduce clause)))])
(let ([xformed (xformer (introducer (syntax-local-introduce clause)))])
(if xformed
(expand-clause orig-stx (certify-clause (syntax-local-introduce (introducer xformed))
certifier
@ -863,7 +863,7 @@
(define-sequence-syntax *in-range
(lambda () #'in-range)
(lambda (orig-stx stx)
(lambda (stx)
(let loop ([stx stx])
(syntax-case stx ()
[[(id) (_ a b step)] #`[(id)
@ -898,7 +898,7 @@
(define-sequence-syntax *in-naturals
(lambda () #'in-naturals)
(lambda (orig-stx stx)
(lambda (stx)
(let loop ([stx stx])
(syntax-case stx ()
[[(id) (_ start)]
@ -929,7 +929,7 @@
(define-sequence-syntax *in-list
(lambda () #'in-list)
(lambda (orig-stx stx)
(lambda (stx)
(syntax-case stx ()
[((id) (_ lst-expr))
#'[(id)
@ -956,7 +956,7 @@
vector-length-id
in-vector-id
vector-ref-id)
(lambda (orig-stx stx)
(lambda (stx)
(with-syntax ([vector? vector?-id]
[in-vector in-vector-id]
[vector-length vector-length-id]
@ -1009,7 +1009,7 @@
(define-sequence-syntax *in-indexed
(lambda () #'in-indexed)
(lambda (orig-stx stx)
(lambda (stx)
(syntax-case stx ()
[((id1 id2) (_ gen-expr))
#'[(id1 id2) (in-parallel gen-expr (*in-naturals))]]))))

View File

@ -195,6 +195,18 @@
(hash-set! ht v v2)
(set-box! v2 (copy-value (unbox v) ht))
v2)]
[(hash? v) (let ([ph (make-placeholder #f)])
(hash-set! ht v ph)
(let ([a (hash-map v (lambda (k v)
(cons (copy-value k ht)
(copy-value v ht))))])
(placeholder-set!
ph
((if (hash-eq? v)
make-hasheq-placeholder
make-hash-placeholder)
a)))
ph)]
[else v]))
(define (strip-comments stx)

View File

@ -0,0 +1,137 @@
#lang scribble/doc
@(require "mz.ss")
@title[#:tag "booleans"]{Booleans and Equality}
True and false are represented by the values @scheme[#t] and
@scheme[#f], respectively, though operations that depend a boolean
value typically treat anything other than @scheme[#f] as true.
See also: @scheme[and], @scheme[or], @scheme[andmap], @scheme[ormap].
@defproc[(boolean? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is @scheme[#t] or @scheme[#f],
@scheme[#f] otherwise.}
@defproc[(not [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is @scheme[#f], @scheme[#f] otherwise.
}
@defproc[(equal? [v1 any/c] [v2 any/c]) boolean?]{
Two values are @scheme[equal?] if and only if they are @scheme[eqv?],
unless otherwise specified for a particular datatype.
Datatypes with further specification of @scheme[equal?] include
strings, byte strings, numbers, pairs, mutable pairs, vectors, hash
tables, and inspectable structures. In the last five cases, equality
is recursively defined; if both @scheme[v1] and @scheme[v2] contain
reference cycles, they are equal when the infinite unfoldings of the
values would be equal. See also @scheme[prop:equal+hash].}
@defproc[(eqv? [v1 any/c] [v2 any/c]) boolean?]{
Two values are @scheme[eqv?] if and only if they are @scheme[eq?],
unless otherwise specified for a particular datatype.
The number and character datatypes are the only ones for which
@scheme[eqv?] differs from @scheme[eq?].}
@defproc[(eq? [v1 any/c] [v2 any/c]) boolean?]{
Return @scheme[#t] if @scheme[v1] and @scheme[v2] refer to the same
object, @scheme[#f] otherwise. See also @secref["model-eq"].}
@defproc[(immutable? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is an immutable @tech{string},
@tech{byte string}, @tech{vector}, @tech{hash table}, or box,
@scheme[#f] otherwise.}
@defthing[prop:equal+hash struct-type-property?]{
A @tech{structure type property} (see @secref["structprops"]) that
supplies an equality predicate and hashing functions for a structure
type. The property value must be a list of three procedures:
@itemize{
@item{@scheme[_equal-proc : (any/c any/c (any/c any/c . ->
. boolean?) . -> . any/c)] --- tests whether the first two
arguments are equal, where both values are instances of the
structure type to which the property is associated (or a
subtype of the structure type).
The third argument is an @scheme[equal?] predicate to use for
recursive equality checks; use the given predicate instead of
@scheme[equal?] to ensure that data cycles are handled
properly.
The @scheme[_equal-proc] is called for a pair of structures
only when they are not @scheme[eq?], and only when they both
have a @scheme[prop:equal+hash] value inherited from the same
structure type. With this strategy, the order in which
@scheme[equal?] receives two structures does not matter. It
also means that, by default, a structure sub-type inherits the
equality predicate of its parent, if any.}
@item{@scheme[_hash-proc : (any/c (any/c . -> . exact-integer?) . ->
. exact-integer?)] --- computes a hash code for the given
structure, like @scheme[equal-hash-code]. The first argument is
an instance of the structure type (or one of its subtypes) to
which the property is associated.
The second argument is a @scheme[equal-hash-code]-like
procedure to use for recursive hash-code computation; use the
given procedure instead of @scheme[equal-hash-code] to ensure
that data cycles are handled properly.}
@item{@scheme[_hash2-proc : (any/c (any/c . -> . exact-integer?) . ->
. exact-integer?)] --- computes a secondary hash code for the
given structure. This procedure is like @scheme[_hash-proc],
but analogous to @scheme[equal-secondary-hash-code].}
}
Take care to ensure that @scheme[_hash-proc] and @scheme[_hash2-proc]
are consistent with @scheme[_equal-proc]. Specifically,
@scheme[_hash-proc] and @scheme[_hash2-proc] should produce the same
value for any two structures for which @scheme[_equal-proc] produces a
true value.
When a structure type has no @scheme[prop:equal+hash] property, then
transparent structures (i.e., structures with an @tech{inspector} that
is controlled by the current @tech{inspector}) are @scheme[equal?]
when they are instances of the same structure type (not counting
sub-types), and when they have @scheme[equal?] field values. For
transparent structures, @scheme[equal-hash-code] and
@scheme[equal-secondary-hash-code] derive hash code using the field
values. For opaque structure types, @scheme[equal?] is the same as
@scheme[eq?], and @scheme[equal-hash-code] and
@scheme[equal-secondary-hash-code] results are based only on
@scheme[eq-hash-code].}
@section{Boolean Synonyms}
@note-lib[scheme/bool]
@defthing[true boolean?]{A synonym for @scheme[#t].}
@defthing[false boolean?]{A synonym for @scheme[#f].}
@defproc[(symbol=? [a symbol?] [b symbol?]) boolean?]{
Returns @scheme[(equal? a b)].}
@defproc[(boolean=? [a boolean?] [b boolean?]) boolean?]{
Returns @scheme[(equal? a b)].}

View File

@ -7,7 +7,7 @@
@local-table-of-contents[]
A @pidefterm{byte string} is a fixed-length arary of bytes. A
A @deftech{byte string} is a fixed-length arary of bytes. A
@pidefterm{byte} is an exact integer between @scheme[0] and
@scheme[255] inclusive.

View File

@ -1,147 +1,15 @@
#lang scribble/doc
@(require "mz.ss")
@title[#:style 'toc #:tag "data"]{Primitive Datatypes}
@title[#:style 'toc #:tag "data"]{Datatypes}
Each of the built-in datatypes comes with a set of procedures for
manipulating members of the datatype.
Each pre-defined datatype comes with a set of procedures for
manipulating instances of the datatype.
@local-table-of-contents[#:style 'immediate-only]
@; ------------------------------------------------------------
@section[#:tag "booleans"]{Booleans}
True and false are represented by the values @scheme[#t] and
@scheme[#f], respectively, though operations that depend a boolean
value typically treat anything other than @scheme[#f] as true.
See also: @scheme[and], @scheme[or], @scheme[andmap], @scheme[ormap].
@defproc[(boolean? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is @scheme[#t] or @scheme[#f],
@scheme[#f] otherwise.}
@defproc[(not [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is @scheme[#f], @scheme[#f] otherwise.
}
@defproc[(equal? [v1 any/c] [v2 any/c]) boolean?]{
Two values are @scheme[equal?] if and only if they are @scheme[eqv?],
unless otherwise specified for a particular datatype.
Datatypes with further specification of @scheme[equal?] include
strings, byte strings, numbers, pairs, mutable pairs, vectors, hash
tables, and inspectable structures. In the last five cases, equality
is recursively defined; if both @scheme[v1] and @scheme[v2] contain
reference cycles, they are equal when the infinite unfoldings of the
values would be equal. See also @scheme[prop:equal+hash].}
@defproc[(eqv? [v1 any/c] [v2 any/c]) boolean?]{
Two values are @scheme[eqv?] if and only if they are @scheme[eq?],
unless otherwise specified for a particular datatype.
The number and character datatypes are the only ones for which
@scheme[eqv?] differs from @scheme[eq?].}
@defproc[(eq? [v1 any/c] [v2 any/c]) boolean?]{
Return @scheme[#t] if @scheme[v1] and @scheme[v2] refer to the same
object, @scheme[#f] otherwise. See also @secref["model-eq"].}
@defproc[(immutable? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is an immutable string, byte string,
vector, or box, @scheme[#f] otherwise.}
@defthing[prop:equal+hash struct-type-property?]{
A @tech{structure type property} (see @secref["structprops"]) that
supplies an equality predicate and hashing functions for a structure
type. The property value must be a list of three procedures:
@itemize{
@item{@scheme[_equal-proc : (any/c any/c (any/c any/c . ->
. boolean?) . -> . any/c)] --- tests whether the first two
arguments are equal, where both values are instances of the
structure type to which the property is associated (or a
subtype of the structure type).
The third argument is an @scheme[equal?] predicate to use for
recursive equality checks; use the given predicate instead of
@scheme[equal?] to ensure that data cycles are handled
properly.
The @scheme[_equal-proc] is called for a pair of structures
only when they are not @scheme[eq?], and only when they both
have a @scheme[prop:equal+hash] value inherited from the same
structure type. With this strategy, the order in which
@scheme[equal?] receives two structures does not matter. It
also means that, by default, a structure sub-type inherits the
equality predicate of its parent, if any.}
@item{@scheme[_hash-proc : (any/c (any/c . -> . exact-integer?) . ->
. exact-integer?)] --- computes a hash code for the given
structure, like @scheme[equal-hash-code]. The first argument is
an instance of the structure type (or one of its subtypes) to
which the property is associated.
The second argument is a @scheme[equal-hash-code]-like
procedure to use for recursive hash-code computation; use the
given procedure instead of @scheme[equal-hash-code] to ensure
that data cycles are handled properly.}
@item{@scheme[_hash2-proc : (any/c (any/c . -> . exact-integer?) . ->
. exact-integer?)] --- computes a secondary hash code for the
given structure. This procedure is like @scheme[_hash-proc],
but analogous to @scheme[equal-secondary-hash-code].}
}
Take care to ensure that @scheme[_hash-proc] and @scheme[_hash2-proc]
are consistent with @scheme[_equal-proc]. Specifically,
@scheme[_hash-proc] and @scheme[_hash2-proc] should produce the same
value for any two structures for which @scheme[_equal-proc] produces a
true value.
When a structure type has no @scheme[prop:equal+hash] property, then
transparent structures (i.e., structures with an @tech{inspector} that
is controlled by the current @tech{inspector}) are @scheme[equal?]
when they are instances of the same structure type (not counting
sub-types), and when they have @scheme[equal?] field values. For
transparent structures, @scheme[equal-hash-code] and
@scheme[equal-secondary-hash-code] derive hash code using the field
values. For opaque structure types, @scheme[equal?] is the same as
@scheme[eq?], and @scheme[equal-hash-code] and
@scheme[equal-secondary-hash-code] results are based only on
@scheme[eq-hash-code].}
@subsection{Boolean Synonyms}
@note-lib[scheme/bool]
@defthing[true boolean?]{A synonym for @scheme[#t].}
@defthing[false boolean?]{A synonym for @scheme[#f].}
@defproc[(symbol=? [a symbol?] [b symbol?]) boolean?]{
Returns @scheme[(equal? a b)].}
@defproc[(boolean=? [a boolean?] [b boolean?]) boolean?]{
Returns @scheme[(equal? a b)].}
@include-section["booleans.scrbl"]
@; ------------------------------------------------------------
@include-section["numbers.scrbl"]
@ -163,10 +31,10 @@ Returns @scheme[(equal? a b)].}
@section-index["symbols" "generating"]
@section-index["symbols" "unique"]
A symbol is like an immutable string, but symbols are normally
@deftech{interned}, so that two symbols with the same character
content are normally @scheme[eq?]. All symbols produced by the default
reader (see @secref["parse-symbol"]) are interned.
A @deftech{symbol} is like an immutable string, but symbols are
normally @deftech{interned}, so that two symbols with the same
character content are normally @scheme[eq?]. All symbols produced by
the default reader (see @secref["parse-symbol"]) are interned.
The two procedures @scheme[string->uninterned-symbol] and
@scheme[gensym] generate @deftech{uninterned} symbols, i.e., symbols
@ -222,8 +90,8 @@ ephemeron key (see @secref["ephemerons"]).
@guideintro["keywords"]{keywords}
A keyword is like an @tech{interned} symbol, but its printed form
starts with @litchar{#:}, and a keyword cannot be used as an
A @deftech{keyword} is like an @tech{interned} symbol, but its printed
form starts with @litchar{#:}, and a keyword cannot be used as an
identifier. Furthermore, a keyword by itself is not a valid
expression, though a keyword can be @scheme[quote]d to form an
expression that produces the symbol.
@ -259,148 +127,8 @@ for each pair of keywords is the same as using
@; ----------------------------------------------------------------------
@include-section["mpairs.scrbl"]
@; ------------------------------------------------------------
@section[#:tag "vectors"]{Vectors}
A vector is a fixed-length array with constant-time access and update
of the vector slots, which are numbered from @scheme[0] to one less
than the number of slots in the vector.
Two vectors are @scheme[equal?] if they have the same length, and if
the values in corresponding slots of the the vectors are
@scheme[equal?].
A vector can be @defterm{mutable} or @defterm{immutable}. When an
immutable vector is provided to a procedure like @scheme[vector-set!],
the @exnraise[exn:fail:contract]. Vectors generated by the default
reader (see @secref["parse-string"]) are immutable.
A vector can be used as a single-valued sequence (see
@secref["sequences"]). The elements of the vector serve as elements
of the sequence. See also @scheme[in-vector].
@defproc[(vector? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a vector, @scheme[#f] otherwise.}
@defproc[(make-vector [size nonnegative-exact-integer?]
[v any/c 0]) vector?]{
Returns a mutable vector with @scheme[size] slots, where all slots are
initialized to contain @scheme[v].}
@defproc[(vector [v any/c] ...) vector?]{
Returns a mutable vector with as many slots as provided @scheme[v]s,
where the slots are initialized to contain the given @scheme[v]s in
order.}
@defproc[(vector-immutable [v any/c] ...) (and/c vector?
immutable?)]{
Returns an immutable vector with as many slots as provided
@scheme[v]s, where the slots are contain the given @scheme[v]s in
order.}
@defproc[(vector-length [vec vector?]) nonnegative-exact-integer?]{
Returns the length of @scheme[vec] (i.e., the number of slots in the
vector).}
@defproc[(vector-ref [vec vector?][pos nonnegative-exact-integer?]) any/c]{
Returns the element in slot @scheme[pos] of @scheme[vec]. The first
slot is position @scheme[0], and the last slot is one less than
@scheme[(vector-length vec)].}
@defproc[(vector-set! [vec (and/c vector? (not/c immutable?))]
[pos nonnegative-exact-integer?]
[v any/c])
void?]{
Updates the slot @scheme[pos] of @scheme[vec] to contain @scheme[v].}
@defproc[(vector->list [vec vector?])
list?]{
Returns a list with the same length and elements as @scheme[vec].}
@defproc[(list->vector [lst list?])
vector?]{
Returns a mutable vector with the same length and elements as
@scheme[lst].}
@defproc[(vector->immutable-vector [vec vector?])
(and/c vector? immutable?)]{
Returns an immutable vector with the same length and elements as @scheme[vec].
If @scheme[vec] is itself immutable, then it is returned as the result.}
@defproc[(vector-fill! [vec (and/c vector? (not/c immutable?))]
[v any/c])
void?]{
Changes all slots of @scheme[vec] to contain @scheme[v].}
@defproc[(vector-copy! [dest (and/c vector? (not/c immutable?))]
[dest-start exact-nonnegative-integer?]
[src vector?]
[src-start exact-nonnegative-integer? 0]
[src-end exact-nonnegative-integer? (vector-length src)])
void?]{
Changes the elements of @scheme[dest] starting at position
@scheme[dest-start] to match the elements in @scheme[src] from
@scheme[src-start] (inclusive) to @scheme[src-end] (exclusive). The
vectors @scheme[dest] and @scheme[src] can be the same vector, and in
that case the destination region can overlap with the source region;
the destination elements after the copy match the source elements
from before the copy. If any of @scheme[dest-start],
@scheme[src-start], or @scheme[src-end] are out of range (taking into
account the sizes of the vectors and the source and destination
regions), the @exnraise[exn:fail:contract].
@examples[(define v (vector 'A 'p 'p 'l 'e))
(vector-copy! v 4 #(y))
(vector-copy! v 0 v 3 4)
v]}
@defproc[(vector->values [vec vector?]
[start-pos nonnegative-exact-integer? 0]
[end-pos nonnegative-exact-integer? (vector-length vec)])
any]{
Returns @math{@scheme[end-pos] - @scheme[start-pos]} values, which are
the elements of @scheme[vec] from @scheme[start-pos] (inclusive) to
@scheme[end-pos] (exclusive). If @scheme[start-pos] or
@scheme[end-pos] are greater than @scheme[(vector-length vec)], or if
@scheme[end-pos] is less than @scheme[start-pos], the
@exnraise[exn:fail:contract].}
@defproc[(build-vector [n exact-nonnegative-integer?]
[proc (exact-nonnegative-integer? . -> . any.c)])
vector?]{
Creates a vector of @scheme[n] elements by applying @scheme[proc] to
the integers from @scheme[0] to @scheme[(sub1 n)] in order. If
@scheme[_vec] is the resulting vector, then @scheme[(vector-ref _vec
_i)] is the value produced by @scheme[(proc _i)].
@examples[
(build-vector 5 add1)
]}
@; ----------------------------------------------------------------------
@include-section["vectors.scrbl"]
@; ------------------------------------------------------------
@section[#:tag "boxes"]{Boxes}
@ -431,279 +159,15 @@ Returns the content of @scheme[box]. For any @scheme[v],
Sets the content of @scheme[box] to @scheme[v].}
@; ----------------------------------------------------------------------
@section[#:tag "hashtables"]{Hash Tables}
A @deftech{hash table} (or simply @deftech{hash}) maps each of its
keys to a single value. For a given hash table, keys are equivalent
via @scheme[equal?] or @scheme[eq?], and keys are retained either
strongly or weakly (see @secref["weakbox"]). A hash table is also
either mutable or immutable; immutable tables support constant-time
functional update.
A hash table can be used as a two-valued @tech{sequence} (see
@secref["sequences"]). The keys and values of the hash table serve as
elements of the sequence (i.e., each element is a key and its
associated value). If a mapping is added to or removed from the hash
table during iteration, then an iteration step may fail with
@scheme[exn:fail:contract], or the iteration may skip or duplicate
keys and values. See also @scheme[in-hash], @scheme[in-hash-keys],
@scheme[in-hash-values], and @scheme[in-hash-pairs].
Two hash tables are @scheme[equal?] if they use the same
key-comparison procedure (@scheme[equal?] or @scheme[eq?]), if they
both hold keys strongly or weakly, and if they have the same
mutability.
@bold{Caveats concerning concurrent modification:} A mutable hash
table can be manipulated with @scheme[hash-ref], @scheme[hash-set!],
and @scheme[hash-remove!] concurrently by multiple threads, and the
operations are protected by a table-specific semaphore as needed. Two
caveats apply, however:
@itemize{
@item{If a thread is terminated while applying @scheme[hash-ref],
@scheme[hash-set!], or @scheme[hash-remove!] to a hash table that
uses @scheme[equal?] key comparisons, all current and future
operations on the hash table block indefinitely.}
@item{The @scheme[hash-map] and @scheme[hash-for-each] procedures do
not use the table's semaphore. Consequently, if a hash table is
extended with new keys by another thread while a map or for-each
traversal is in process, arbitrary key--value pairs can be dropped
or duplicated in the traversal. Similarly, if a map or for-each
procedure itself extends the table, arbitrary key--value pairs can
be dropped or duplicated. However, key mappings can be deleted or
remapped by any thread with no adverse affects (i.e., the change
does not affect a traversal if the key has been seen already,
otherwise the traversal skips a deleted key or uses the remapped
key's new value).}
}
@bold{Caveat concerning mutable keys:} If a key into an
@scheme[equal?]-based hash table is mutated (e.g., a key string is
modified with @scheme[string-set!]), then the hash table's behavior
for insertion and lookup operations becomes unpredictable.
@defproc[(hash? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a @tech{hash table}, @scheme[#f]
otherwise.}
@defproc[(hash-eq? [hash hash?]) boolean?]{
Returns @scheme[#t] if @scheme[hash] compares keys with @scheme[eq?],
@scheme[#f] if it compares with @scheme[equal?].}
@defproc[(hash-weak? [hash hash?]) boolean?]{
Returns @scheme[#t] if @scheme[hash] retains its keys weakly,
@scheme[#f] if it retains keys strongly.}
@defproc[(make-hash) hash?]{
Creates an empty mutable hash table that holds keys strongly and that
uses @scheme[equal?] to compare keys.}
@defproc[(make-hasheq) (and/c hash? hash-eq?)]{
Creates an empty mutable hash table that holds keys strongly and that
uses @scheme[eq?] to compare keys.}
@defproc[(make-weak-hash) (and/c hash? hash-weak?)]{
Creates an empty mutable hash table that holds keys weakly and that
uses @scheme[equal?] to compare keys.}
@defproc[(make-weak-hasheq) (and/c hash? hash-eq? hash-weak?)]{
Creates an empty mutable hash table that holds keys weakly and that
uses @scheme[eq?] to compare keys.}
@defproc[(make-immutable-hash [assocs (listof pair?)])
(and/c hash? immutable?)]{
Creates an immutable hash table that compares keys with
@scheme[equal?]. In each element of @scheme[assocs], the @scheme[car]
of each pair is a key, and the @scheme[cdr] is the corresponding
value. The mappings are added to the table in the order that they
appear in @scheme[assocs], so later mappings can hide earlier
mappings.}
@defproc[(make-immutable-hasheq [assocs (listof pair?)])
(and/c hash? hash-eq? immutable?)]{
Like @scheme[make-immutable-hash], but the resulting hash table
compares keys with @scheme[eq?].}
@defproc[(hash-set! [hash (and/c hash? (not/c immutable?))]
[key any/c]
[v any/c]) void?]{
Maps @scheme[key] to @scheme[v] in @scheme[hash], overwriting
any existing mapping for @scheme[key].}
@defproc[(hash-set [hash (and/c hash? immutable?)]
[key any/c]
[v any/c])
(and/c hash? immutable?)]{
Functionally extends @scheme[hash] by mapping @scheme[key] to
@scheme[v], overwriting any existing mapping for @scheme[key], and
returning an extended hash table.}
@defproc[(hash-ref [hash hash?]
[key any/c]
[failure-result any/c (lambda () (raise (make-exn:fail ....)))])
any]{
Returns the value for @scheme[key] in @scheme[hash]. If no value
is found for @scheme[key], then @scheme[failure-result] determines the
result:
@itemize{
@item{If @scheme[failure-result] is a procedure, it is called
(through a tail call) with no arguments to produce the result.}
@item{Otherwise, @scheme[failure-result] is returned as the result.}
}}
@defproc[(hash-remove! [hash (and/c hash? (not/c immutable?))]
[key any/c])
void?]{
Removes any existing mapping for @scheme[key] in @scheme[hash].}
@defproc[(hash-remove [hash (and/c hash? immutable?)]
[key any/c])
(and/c hash? immutable?)]{
Functionally removes any existing mapping for @scheme[key] in
@scheme[hash], returning the updated hash table.}
@defproc[(hash-map [hash hash?]
[proc (any/c any/c . -> . any/c)])
(listof any/c)]{
Applies the procedure @scheme[proc] to each element in
@scheme[hash] in an unspecified order, accumulating the results
into a list. The procedure @scheme[proc] is called each time with a
key and its value. See the caveat above about concurrent
modification.}
@defproc[(hash-for-each [hash hash?]
[proc (any/c any/c . -> . any)])
void?]{
Applies @scheme[proc] to each element in @scheme[hash] (for the
side-effects of @scheme[proc]) in an unspecified order. The procedure
@scheme[proc] is called each time with a key and its value. See the
caveat above about concurrent modification.}
@defproc[(hash-count [hash hash?])
nonnegative-exact-integer?]{
Returns the number of keys mapped by @scheme[hash]. If
@scheme[hash] is not created with @scheme['weak], then the
result is computed in constant time and atomically. If
@scheme[hash] is created with @scheme['weak], see the caveat
above about concurrent modification.}
@defproc[(hash-iterate-first [hash hash?])
(or/c false/c nonnegative-exact-integer?)]{
Returns @scheme[#f] if @scheme[hash] contains no elements, otherwise
it returns an integer that is a index to the first element in the hash
table; ``first'' refers to an unspecified ordering of the table
elements, and the index values are not necessarily consecutive
integers. For a mutable @scheme[hash], this index is guaranteed to
refer to the first item only as long as no items are added to or
removed from @scheme[hash].}
@defproc[(hash-iterate-next [hash hash?]
[prev nonnegative-exact-integer?])
(or/c false/c nonnegative-exact-integer?)]{
Returns either an integer that is an index to the element in
@scheme[hash] after the element indexed by @scheme[pos] (which is not
necessarily one more than @scheme[pos]) or @scheme[#f] if @scheme[pos]
refers to the last element in @scheme[hash]. If @scheme[pos] is not a
valid index, then the @exnraise[exn:fail:contract]. For a mutable
@scheme[hash], the result index is guaranteed to refer to its item
only as long as no items are added to or removed from @scheme[hash].}
@defproc[(hash-iterate-key [hash hash?]
[pos nonnegative-exact-integer?])
any]{
Returns the key for the element in @scheme[hash] at index
@scheme[pos]. If @scheme[pos] is not a valid index for
@scheme[hash], the @exnraise[exn:fail:contract].}
@defproc[(hash-iterate-value [hash hash?]
[pos nonnegative-exact-integer?])
any]{
Returns the value for the element in @scheme[hash] at index
@scheme[pos]. If @scheme[pos] is not a valid index for
@scheme[hash], the @exnraise[exn:fail:contract].}
@defproc[(hash-copy [hash hash?])
(and/c hash? (not/c immutable?))]{
Returns a mutable hash table with the same mappings, same
key-comparison mode, and same key-holding strength as @scheme[hash].}
@defproc[(eq-hash-code [v any/c]) exact-integer?]{
Returns an exact integer; for any two @scheme[eq?] values, the
returned integer is the same. Furthermore, for the result integer
@scheme[k] and any other exact integer @scheme[j], @scheme[(= k j)]
implies @scheme[(eq? k j)].}
@defproc[(equal-hash-code [v any/c]) exact-integer?]{
Returns an exact integer; for any two @scheme[equal?] values, the
returned integer is the same. Furthermore, for the result integer
@scheme[k] and any other exact integer @scheme[j], @scheme[(= k j)]
implies @scheme[(eq? k j)]. A has code is computed even when
@scheme[v] contains a cycle through pairs, vectors, boxes, and/or
inspectable structure fields. See also @scheme[prop:equal+hash].}
@defproc[(equal-secondary-hash-code [v any/c]) exact-integer?]{
Like @scheme[equal-hash-code], but computes a secondary value suitable
for use in double hashing.}
@include-section["hashes.scrbl"]
@; ----------------------------------------------------------------------
@include-section["sequences.scrbl"]
@; ----------------------------------------------------------------------
@include-section["dicts.scrbl"]
@; ----------------------------------------------------------------------
@include-section["procedures.scrbl"]

View File

@ -0,0 +1,468 @@
#lang scribble/doc
@(require "mz.ss"
scribble/eval)
@(define dict-eval (make-base-eval))
@interaction-eval[#:eval dict-eval (require scheme/dict)]
@title[#:tag "dicts"]{Dictionaries}
A @deftech{dictionary} is an instance of a datatype that maps keys to
values. The following datatypes are all dictionaries:
@itemize[
@item{@techlink{hash tables};}
@item{@techlink{vectors} (using only exact integers as keys);}
@item{@techlink{lists} of @techlink{pairs} (an @deftech{association
list} using @scheme[equal?] to compare keys); and}
@item{@techlink{structures} whose types have the @scheme[prop:dict]
property.}
]
@note-lib[scheme/dict]
@defproc[(dict? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a @tech{dictionary}, @scheme[#f]
otherwise.
Beware that @scheme[dict?] is not a constant-time test on pairs, since
checking that @scheme[v] is an @tech{association list} may require
traversing the list.
@examples[
#:eval dict-eval
(dict? #hash((a . "apple")))
(dict? '#("apple" "banana"))
(dict? '("apple" "banana"))
(dict? '((a . "apple") (b . "banana")))
]}
@defproc[(dict-mutable? [d dict?]) boolean?]{
Returns @scheme[#t] if @scheme[d] is mutable via @scheme[dict-set!]
and maybe @scheme[dict-remove!], @scheme[#f] otherwise.
@examples[
#:eval dict-eval
(dict-mutable? #hash((a . "apple")))
(dict-mutable? (make-hash))
(dict-mutable? '#("apple" "banana"))
(dict-mutable? (vector "apple" "banana"))
(dict-mutable? '((a . "apple") (b . "banana")))
]}
@defproc[(dict-can-remove-keys? [d dict?]) boolean?]{
Returns @scheme[#t] if @scheme[d] supports removing mappings via
@scheme[dict-remove!] and/or @scheme[dict-remove], @scheme[#f]
otherwise.
@examples[
#:eval dict-eval
(dict-can-remove-keys? #hash((a . "apple")))
(dict-can-remove-keys? '#("apple" "banana"))
(dict-can-remove-keys? '((a . "apple") (b . "banana")))
]}
@defproc[(dict-can-functional-set? [d dict?]) boolean?]{
Returns @scheme[#t] if @scheme[d] supports functional update via
@scheme[dict-set] and maybe @scheme[dict-remove], @scheme[#f]
otherwise.
@examples[
#:eval dict-eval
(dict-can-functional-set? #hash((a . "apple")))
(dict-can-functional-set? (make-hash))
(dict-can-functional-set? '#("apple" "banana"))
(dict-can-functional-set? '((a . "apple") (b . "banana")))
]}
@defproc[(dict-set! [dict (and/c dict? (not/c immutable?))]
[key any/c]
[v any/c]) void?]{
Maps @scheme[key] to @scheme[v] in @scheme[dict], overwriting any
existing mapping for @scheme[key]. The update can fail with a
@scheme[exn:fail:contract] exception if @scheme[dict] is not mutable
or if @scheme[key] is not an allowed key for the dictionary (e.g., not
an exact integer in the appropriate range when @scheme[dict] is a
@tech{vector}).
@examples[
#:eval dict-eval
(define h (make-hash))
(dict-set! h 'a "apple")
h
(define v (vector #f #f #f))
(dict-set! v 0 "apple")
v
]}
@defproc[(dict-set [dict (and/c dict? immutable?)]
[key any/c]
[v any/c])
(and/c dict? immutable?)]{
Functionally extends @scheme[dict] by mapping @scheme[key] to
@scheme[v], overwriting any existing mapping for @scheme[key], and
returning an extended dictionary. The update can fail with a
@scheme[exn:fail:contract] exception if @scheme[dict] does not support
functional extension or if @scheme[key] is not an allowed key for the
dictionary.
@examples[
#:eval dict-eval
(dict-set #hash() 'a "apple")
(dict-set #hash((a . "apple") (b . "beer")) 'b "banana")
(dict-set '() 'a "apple")
(dict-set '((a . "apple") (b . "beer")) 'b "banana")
]}
@defproc[(dict-ref [dict dict?]
[key any/c]
[failure-result any/c (lambda () (raise (make-exn:fail ....)))])
any]{
Returns the value for @scheme[key] in @scheme[dict]. If no value
is found for @scheme[key], then @scheme[failure-result] determines the
result:
@itemize{
@item{If @scheme[failure-result] is a procedure, it is called
(through a tail call) with no arguments to produce the result.}
@item{Otherwise, @scheme[failure-result] is returned as the result.}
}
@examples[
#:eval dict-eval
(dict-ref #hash((a . "apple") (b . "beer")) 'a)
(dict-ref #hash((a . "apple") (b . "beer")) 'c)
(dict-ref #hash((a . "apple") (b . "beer")) 'c #f)
(dict-ref '((a . "apple") (b . "banana")) 'b)
(dict-ref #("apple" "banana") 1)
(dict-ref #("apple" "banana") 3 #f)
(dict-ref #("apple" "banana") -3 #f)
]}
@defproc[(dict-remove! [dict (and/c dict? (not/c immutable?))]
[key any/c])
void?]{
Removes any existing mapping for @scheme[key] in @scheme[dict]. The
update can fail if @scheme[dict] is not mutable or does not support
removing keys (as is the case for @tech{vectors}, for example).
@examples[
#:eval dict-eval
(define h (make-hash))
(dict-set! h 'a "apple")
h
(dict-remove! h 'a)
h]}
@defproc[(dict-remove [dict (and/c dict? immutable?)]
[key any/c])
(and/c dict? immutable?)]{
Functionally removes any existing mapping for @scheme[key] in
@scheme[dict], returning the updated dictionary. The update can fail
if @scheme[dict] does not support functional update or does not
support removing keys.
@examples[
#:eval dict-eval
(define h #hash())
(define h (dict-set h 'a "apple"))
h
(dict-remove h 'a)
h
(dict-remove h 'z)
(dict-remove '((a . "apple") (b . "banana")) 'a)
]}
@defproc[(dict-map [dict dict?]
[proc (any/c any/c . -> . any/c)])
(listof any/c)]{
Applies the procedure @scheme[proc] to each element in
@scheme[dict] in an unspecified order, accumulating the results
into a list. The procedure @scheme[proc] is called each time with a
key and its value.
@examples[
#:eval dict-eval
(dict-map #hash((a . "apple") (b . "banana")) vector)
]}
@defproc[(dict-for-each [dict dict?]
[proc (any/c any/c . -> . any)])
void?]{
Applies @scheme[proc] to each element in @scheme[dict] (for the
side-effects of @scheme[proc]) in an unspecified order. The procedure
@scheme[proc] is called each time with a key and its value.
@examples[
#:eval dict-eval
(dict-for-each #hash((a . "apple") (b . "banana"))
(lambda (k v)
(printf "~a = ~s\n" k v)))
]}
@defproc[(dict-count [dict dict?])
nonnegative-exact-integer?]{
Returns the number of keys mapped by @scheme[dict], usually in
constant time.
@examples[
#:eval dict-eval
(dict-count #hash((a . "apple") (b . "banana")))
(dict-count #("apple" "banana"))
]}
@defproc[(dict-iterate-first [dict dict?]) any/c]{
Returns @scheme[#f] if @scheme[dict] contains no elements, otherwise
it returns a non-@scheme[#f] value that is a index to the first
element in the dict table; ``first'' refers to an unspecified ordering
of the dictionary elements. For a mutable @scheme[dict], this index is
guaranteed to refer to the first item only as long as no mappings are
added to or removed from @scheme[dict].
@examples[
#:eval dict-eval
(dict-iterate-first #hash((a . "apple") (b . "banana")))
(dict-iterate-first #hash())
(dict-iterate-first #("apple" "banana"))
(dict-iterate-first '((a . "apple") (b . "banana")))
]}
@defproc[(dict-iterate-next [dict dict?]
[pos any/c])
any/c]{
Returns either a non-@scheme[#f] that is an index to the element in
@scheme[dict] after the element indexed by @scheme[pos] or @scheme[#f]
if @scheme[pos] refers to the last element in @scheme[dict]. If
@scheme[pos] is not a valid index, then the
@exnraise[exn:fail:contract]. For a mutable @scheme[dict], the result
index is guaranteed to refer to its item only as long as no items are
added to or removed from @scheme[dict]. The @scheme[dict-iterate-next]
operation should take constant time.
@examples[
#:eval dict-eval
(define h #hash((a . "apple") (b . "banana")))
(define i (dict-iterate-first h))
i
(dict-iterate-next h i)
(dict-iterate-next h (dict-iterate-next h i))
]}
@defproc[(dict-iterate-key [dict dict?]
[pos any/c])
any]{
Returns the key for the element in @scheme[dict] at index
@scheme[pos]. If @scheme[pos] is not a valid index for @scheme[dict],
the @exnraise[exn:fail:contract]. The @scheme[dict-iterate-key]
operation should take constant time.
@examples[
#:eval dict-eval
(define h '((a . "apple") (b . "banana")))
(define i (dict-iterate-first h))
(dict-iterate-key h i)
(dict-iterate-key h (dict-iterate-next h i))
]}
@defproc[(dict-iterate-value [dict dict?]
[pos any/c])
any]{
Returns the value for the element in @scheme[dict] at index
@scheme[pos]. If @scheme[pos] is not a valid index for @scheme[dict],
the @exnraise[exn:fail:contract]. The @scheme[dict-iterate-key]
operation should take constant time.
@examples[
#:eval dict-eval
(define h '((a . "apple") (b . "banana")))
(define i (dict-iterate-first h))
(dict-iterate-value h i)
(dict-iterate-value h (dict-iterate-next h i))
]}
@defproc[(in-dict [dict dict?]) sequence?]{ Returns a @tech{sequence}
whose each element is two values: a key and corresponding value from
@scheme[dict].
@examples[
#:eval dict-eval
(define h #hash((a . "apple") (b . "banana")))
(for/list ([(k v) (in-dict h)])
(format "~a = ~s" k v))
]}
@defproc[(in-dict-keys [dict dict?]) sequence?]{
Returns a sequence whose elements are the keys of @scheme[dict].
@examples[
#:eval dict-eval
(define h #hash((a . "apple") (b . "banana")))
(for/list ([k (in-dict-keys h)])
k)
]}
@defproc[(in-dict-values [dict dict?]) sequence?]{
Returns a sequence whose elements are the values of @scheme[dict].
@examples[
#:eval dict-eval
(define h #hash((a . "apple") (b . "banana")))
(for/list ([v (in-dict-values h)])
v)
]}
@defproc[(in-dict-pairs [dict dict?]) sequence?]{ Returns a sequence
whose elements are pairs, each containing a key and its value from
@scheme[dict] (as opposed to using @scheme[in-dict], which gets the
key and value as separate values for each element).
@examples[
#:eval dict-eval
(define h #hash((a . "apple") (b . "banana")))
(for/list ([p (in-dict-pairs h)])
p)
]}
@defthing[prop:dict struct-type-property?]{
A @tech{structure type property} (see @secref["structprops"]) that
supplies dictionary-operation implementations for a structure
type. The property value must be a vector of ten procedures (some
optional) that are applied only to instances of the structure type
that has the property:
@itemize[
@item{@scheme[_ref] : a procedure like @scheme[dict-ref] that accepts
either two or three arguments}
@item{@scheme[_set!] : a procedure like @scheme[dict-set!] that accepts
three arguments, or @scheme[#f] if mutation is not supported}
@item{@scheme[_set] : a procedure like @scheme[dict-set] that accepts
three arguments and returns an updated dictionary, or
@scheme[#f] if functional update is not supported}
@item{@scheme[_remove!] : a procedure like @scheme[dict-remove!] that
accepts two arguments, or @scheme[#f] if mutation is not
supported or if key removal is not supported}
@item{@scheme[_remove] : a procedure like @scheme[dict-remove] that
accepts two arguments and returns an updated dictionary, or
@scheme[#f] if functional update or key removal is not
supported}
@item{@scheme[_count] : a procedure like @scheme[dict-count] that accepts
one argument}
@item{@scheme[_iterate-first] : a procedure like
@scheme[dict-iterate-first] that accepts one argument}
@item{@scheme[_iterate-next] : a procedure like
@scheme[dict-iterate-next] that accepts two arguments; the
procedure is responsible for checking that the second argument
is a valid position for the first argument}
@item{@scheme[_iterate-key] : a procedure like
@scheme[dict-iterate-key] that accepts two arguments; the
procedure is responsible for checking that the second argument
is a valid position for the first argument}
@item{@scheme[_iterate-value] : a procedure like
@scheme[dict-iterate-value] that accepts two arguments; the
procedure is responsible for checking that the second argument
is a valid position for the first argument}
]}
@deftogether[(
@defproc[(make-custom-hash [eql? (any/c any/c . -> . any/c)]
[hash-proc (any/c . -> . exact-integer?)]
[hash2-proc (any/c . -> . exact-integer?)])
dict?]
@defproc[(make-immutable-custom-hash [eql? (any/c any/c . -> . any/c)]
[hash-proc (any/c . -> . exact-integer?)]
[hash2-proc (any/c . -> . exact-integer?)])
dict?]
@defproc[(make-weak-custom-hash [eql? (any/c any/c . -> . any/c)]
[hash-proc (any/c . -> . exact-integer?)]
[hash2-proc (any/c . -> . exact-integer?)])
dict?]
)]{
Creates a dictionary that is implemented in terms of a hash table
where keys are compared with @scheme[eql?] and hashed with
@scheme[hash-proc] and @scheme[hash2-proc]. See
@scheme[prop:equal+hash] for information on suitable equality and
hashing functions.
The @scheme[make-custom-hash] and @scheme[make-weak-custom-hash]
functions create a mutable dictionary that does not support functional
update, while @scheme[make-immutable-custom-hash] creates an immutable
dictionary that supports functional update. The dictionary created by
@scheme[make-weak-custom-hash] retains its keys weakly, like the result
of @scheme[make-weak-hash].
Dictionaries created by @scheme[make-custom-hash] and company are
@scheme[equal?] when they have the same mutability and key strength,
the associated procedures are @scheme[equal?], and the key--value
mappings are the same when keys and values are compared with
@scheme[equal?].
@examples[
#:eval dict-eval
(define h (make-custom-hash (lambda (a b)
(string=? (format "~a" a)
(format "~a" b)))
(lambda (a)
(equal-hash-code
(format "~a" a)))))
(dict-set! h 1 'one)
(dict-ref h "1")
]}

View File

@ -0,0 +1,772 @@
#lang scribble/doc
@(require scribble/struct
(for-syntax scheme/base)
"mz.ss"
"prog-steps.ss")
@(define reduces (make-element #f (list 'rarr)))
@(define rspace (make-element "ghost" (list 'rarr)))
@(define *redex (lambda (c)
(make-element "highlighted" (list c))))
@(define-syntax redex
(syntax-rules () [(_ a) (*redex (scheme a))]))
@(define hole (make-element #f (list "[]")))
@(define (*sub c e) (make-element #f (list c "[" e "]")))
@(define langle (make-element 'tt (list "<")))
@(define rangle (make-element 'tt (list ">")))
@(define comma (make-element 'tt (list ", ")))
@(define-syntax sub
(syntax-rules () [(_ a b) (*sub (scheme a) (scheme b))]))
@(define (*state c e) (make-element #f (list langle c comma e rangle)))
@(define-syntax state
(syntax-rules () [(_ a b) (*state (scheme a) (scheme b))]))
@(define (frame n)
(make-element "schemevariable"
(list "C" (make-element 'subscript (list (format "~a" n))))))
@;------------------------------------------------------------------------
@title[#:tag "eval-model"]{Evaluation Model}
Scheme evaluation can be viewed as the simplification of expressions
to obtain values. For example, just as an elementary-school student
simplifies
@verbatim{ 1 + 1 = 2}
Scheme evaluation simplifies
@schemeblock[
(+ 1 1) #, @reduces 2
]
The arrow @reduces above replaces the more traditional @tt{=} to
emphasize that evaluation proceeds in a particular direction towards
simpler expressions. In particular, a @deftech{value} is an
expression that evaluation simplifies no further, such as the number
@scheme[2].
@;------------------------------------------------------------------------
@section[#:tag "cont-model"]{Sub-expression Evaluation and Continuations}
Some simplifications require more than one step. For example:
@schemeblock[
(- 4 #,(redex (+ 1 1))) #,reduces #,(redex (- 4 2)) #,reduces 2
]
An expression that is not a @tech{value} can always be partitioned
into two parts: a @deftech{redex}, which is the part that changed in a
single-step simplification (show in blue), and the
@deftech{continuation}, which is the surrounding expression
context. In @scheme[(- 4 (+ 1 1))], the redex is @scheme[(+ 1 1)], and
the continuation is @scheme[(- 4 #, @hole)], where @hole takes the
place of the redex. That is, the continuation says how to ``continue''
after the @tech{redex} is reduced to a @tech{value}.
Before some things can be evaluated, some sub-expressions must be
evaluated; for example, in the application @scheme[(- 4 (+ 1 1))], the
application of @scheme[-] cannot be reduced until the sub-expression
@scheme[(+ 1 1)] is reduced.
Thus, the specification of each syntactic form specifies how (some of)
its sub-expressions are evaluated, and then how the results are
combined to reduce the form away.
The @deftech{dynamic extent} of an expression is the sequence of
evaluation steps during which an expression contains the @tech{redex}.
@;------------------------------------------------------------------------
@section{Tail Position}
An expression @scheme[_expr1] is in @deftech{tail position} with
respect to an enclosing expression @scheme[_expr2] if, whenever
@scheme[_expr1] becomes a redex, its @tech{continuation} is the same
as was the enclosing @scheme[_expr2]'s @tech{continuation}.
For example, the @scheme[(+ 1 1)] expression is @italic{not} in @tech{tail
position} with respect to @scheme[(- 4 (+ 1 1))]. To illustrate, we use
the notation @sub[_C _expr] to mean the expression that is produced by
substituting @scheme[_expr] in place of @hole in the @tech{continuation}
@scheme[_C]:
@schemeblock[
#, @sub[_C (- 4 (+ 1 1))] #, @reduces #, @sub[_C (- 4 2)]
]
In this case, the @tech{continuation} for reducing @scheme[(+ 1 1)] is
@sub[_C (+ 4 #, @hole)], not just @scheme[_C].
In contrast, @scheme[(+ 1 1)] is in @tech{tail position} with respect
to @scheme[(if (zero? 0) (+ 1 1) 3)], because, for any continuation
@scheme[_C],
@schemeblock[
#, @sub[_C (if (zero? 0) (+ 1 1) 3)] #, @reduces #, @sub[_C (if #t (+ 1 1) 3)] #, @reduces #, @sub[_C (+ 1 1)]
]
The steps in this reduction sequence are driven by the definition of
@scheme[if], and they do not depend on the @tech{continuation}
@scheme[_C]. The ``then'' branch of an @scheme[if] form is always in
@tech{tail position} with respect to the @scheme[if] form. Due to a
similar reduction rule for @scheme[if] and @scheme[#f], the ``else''
branch of an @scheme[if] form is also in @tech{tail position}.
@tech{Tail-position} specifications provide a guarantee about the
asymptotic space consumption of a computation. In general, the
specification of @tech{tail positions} goes with each syntactic form,
like @scheme[if].
@;------------------------------------------------------------------------
@section[#:tag "values-model"]{Multiple Return Values}
A Scheme expression can evaluate to @deftech{multiple values}, in the
same way that a procedure can accept multiple arguments.
Most @tech{continuations} expect a particular number of result
@tech{values}. Indeed, most @tech{continuations}, such as @scheme[(+
#, @hole 1)] expect a single @tech{value}. The @tech{continuation}
@scheme[(let-values ([(x y) #, @hole]) _expr)] expects two result
@tech{values}; the first result replaces @scheme[x] in the body
@scheme[_expr], and the second replaces @scheme[y] in
@scheme[_expr]. The @tech{continuation} @scheme[(begin #, @hole (+ 1
2))] accepts any number of result @tech{values}, because it ignores
the result(s).
In general, the specification of a syntactic form inidicates the
number of @tech{values} that it produces and the number that it
expects from each of its sub-expression. In addtion, some procedures
(notably @scheme[values]) produce multiple @tech{values}, and some
procedures (notably @scheme[call-with-values]) create continuations
internally that accept a certain number of @tech{values}.
@;------------------------------------------------------------------------
@section{Top-Level Variables}
Given
@verbatim{ x = 10}
then an algebra student simplifies @tt{x + 1} as follows:
@verbatim{ x + 1 = 10 + 1 = 11}
Scheme works much the same way, in that a set of @tech{top-level
variables} are available for substitutions on demand during
evaluation. For example, given
@schemeblock[
(define x 10)
]
then
@schemeblock[
#,(redex (+ x 1)) #,reduces #,(redex (+ 10 1)) #,reduces 11
]
In Scheme, the way definitions appear is just as important as the way
that they are used. Scheme evaluation thus keeps track of both
definitions and the current expression, and it extends the set of
definitions in response to evaluating forms such as @scheme[define].
Each evaluation step, then, takes the current set of definitions and
program to a new set of definitions and program. Before a
@scheme[define] can be moved into the set of definitions, its
right-hand expression must be reduced to a @tech{value}.
@prog-steps/no-obj[
[{}
(begin (define x (code:hilite (+ 9 1))) (+ x 1))]
[{}
(begin (code:hilite (define x 10)) (+ x 1))]
[{(define x 10)}
(code:hilite (begin (void) (+ x 1)))]
[{(define x 10)}
(+ (code:hilite x) 1)]
[{(define x 10)}
(code:hilite (+ 10 1))]
[{(define x 10)}
11]
]
Using @scheme[set!], a program can change the value associated with an
existing @tech{top-level variable}:
@prog-steps/no-obj[
[{(define x 10)}
(begin (code:hilite (set! x 8)) x)]
[{(define x 8)}
(code:hilite (begin (void) x))]
[{(define x 8)}
(code:hilite x)]
[{(define x 8)}
8]
]
@;------------------------------------------------------------------------
@section{Objects and Imperative Update}
In addition to @scheme[set!] for imperative update of @tech{top-level
variables}, various procedures enable the modification of elements
within a compound data structure. For example, @scheme[vector-set!]
modifies the content of a vector.
To allow such modifications to data, we must distingiush between
@tech{values}, which are the results of expressions, and
@deftech{objects}, which hold the data referenced by a value.
A few kinds of @tech{objects} can serve directly as values, including
booleans, @scheme[(void)], and small exact integers. More generally,
however, a @tech{value} is a reference to an @tech{object}. For
example, a @tech{value} can be a reference to a particular vector that
currently holds the value @scheme[10] in its first slot. If an
@tech{object} is modified, then the modification is visible through
all copies of the @tech{value} that reference the same @tech{object}.
In the evaluation model, a set of @tech{objects} must be carried along
with each step in evaluation, just like the definition set. Operations
that create @tech{objects}, such as @scheme[vector], add to the set of
@tech{objects}:
@prog-steps[
[{}
{}
(begin (define x (code:hilite (vector 10 20)))
(define y x)
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{}
(begin (code:hilite (define x <o1>))
(define y x)
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(code:hilite (begin (void)
(define y x)
(vector-set! x 0 11)
(vector-ref y 0)))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(begin (define y (code:hilite x))
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(begin (code:hilite (define y <o1>))
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (begin (void)
(vector-set! x 0 11)
(vector-ref y 0)))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(begin (vector-set! (code:hilite x) 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(begin (code:hilite (vector-set! <o1> 0 11))
(vector-ref y 0))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (begin (void)
(vector-ref y 0)))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(vector-ref (code:hilite y) 0)]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (vector-ref <o1> 0))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
11]
]
The distinction between a @tech{top-level variable} and an object
reference is crucial. A @tech{top-level variable} is not a
@tech{value}; each time a @tech{variable} expression is evaluated, the
value is extracted from the current set of definitions. An object
reference, in contrast is a value, and therefore needs no further
evaluation. The model evaluation steps above use angle-bracketed
@scheme[<o1>] for an object reference to distinguish it from a
@tech{variable} name.
A direct object reference can never appear in a text-based source
program. A program representation created with
@scheme[datum->syntax-object], however, can embed direct references to
existing @tech{objects}.
@;------------------------------------------------------------------------
@section[#:tag "model-eq"]{Object Identity and Comparisons}
The @scheme[eq?] operator compares two @tech{values}, returning
@scheme[#t] when the values refer to the same @tech{object}. This form
of equality is suitable for comparing objects that support imperative
update (e.g., to determine that the effect of modifying an object
through one reference is visible through another reference). Also, an
@scheme[eq?] test evaluates quickly, and @scheme[eq?]-based hashing
is more lightweight than @scheme[equal?]-based hashing in hash tables.
In some cases, however, @scheme[eq?] is unsuitable as a comparison
operator, because the generation of @tech{objects} is not clearly
defined. In particular, two applications of @scheme[+] to the same two
exact integers may or may not produce results that are @scheme[eq?],
although the results are always @scheme[equal?]. Similarly, evaluation
of a @scheme[lambda] form typically generates a new procedure
@tech{object}, but it may re-use a procedure @tech{object} previously
generated by the same source @scheme[lambda] form.
The behavior of a datatype with respect to @scheme[eq?] is generally
specified with the datatype and its associated procedures.
@;------------------------------------------------------------------------
@section[#:tag "gc-model"]{Garbage Collection}
In the program state
@prog-steps[
[{(define <o1> (vector 10 20))
(define <o2> (vector 0))}
{(define x <o1>)}
(+ 1 x)]
]
evaluation cannot depend on @scheme[<o2>], because it is not part of
the program to evaluate, and it is not referenced by any definition
that is accessible in the program. The @tech{object} @scheme[<o2>] may
therefore be removed from the evaluation by @deftech{garbage
collection}.
A few special compound datatypes hold @deftech{weak references} to
objects. Such weak references are treated specially by the garbage
collector in determining which @tech{objects} are reachable for the
remainder of the computation. If an @tech{object} is reachable only
via a @tech{weak reference}, then the object can be reclaimed, and the
@tech{weak reference} is replaced by a different @tech{value}
(typically @scheme[#f]).
@;------------------------------------------------------------------------
@section{Procedure Applications and Local Variables}
Given
@verbatim{ f(x) = x + 10}
then an algebra student simplifies @tt{f(1)} as follows:
@verbatim{ f(7) = 7 + 10 = 17}
The key step in this simplification is take the body of the defined
function @tt{f}, and then replace each @tt{x} with the actual
@tech{value} @tt{1}.
Scheme procedure application works much the same way. A procedure is
an @tech{object}, so evaluating @scheme[(f 7)] starts with a
@tech{variable} lookup:
@prog-steps[
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
((code:hilite f) 7)]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
]
Unlike in algebra, however, the @tech{value} associated with an
argument can be changed in the body of a procedure by using
@scheme[set!], as in the example @scheme[(lambda (x) (begin (set! x 3)
x))]. Since the @tech{value} associated with @scheme[x] can be
changed, an actual value for cannot be substituted for @scheme[x] when
the procedure is applied.
Instead, a new @deftech{location} is created for each @tech{variable}
on each application. The argument @tech{value} is placed in the
@tech{location}, and each instance of the @tech{variable} in the
procedure body is replaced with the new @tech{location}:
@prog-steps[
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
(+ (code:hilite xloc) 10)]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
(code:hilite (+ 7 10))]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
17]
]
A @tech{location} is the same as a @tech{top-level variable}, but when
a @tech{location} is generated, it (conceptually) uses a name that has
not been used before and that cannot not be generated again or
accessed directly.
Generating a @tech{location} in this way means that @scheme[set!]
evaluates for @tech{local variables} in the same way as for
@tech{top-level variables}, because the @tech{local variable} is
always replaced with a @tech{location} by the time the @scheme[set!]
form is evaluated:
@prog-steps[
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)}
((code:hilite f) 7)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 7)}
(begin (code:hilite (set! xloc 3)) xloc)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
(code:hilite (begin (void) xloc))]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
(code:hilite xloc)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
3]
]
The substition and @tech{location}-generation step of procedure
application requires that the argument is a @tech{value}. Therefore,
in @scheme[((lambda (x) (+ x 10)) (+ 1 2))], the @scheme[(+ 1 2)]
sub-expression must be simplified to the @tech{value} @scheme[3], and
then @scheme[3] can be placed into a @tech{location} for
@scheme[x]. In other words, Scheme is a @deftech{call-by-value}
language.
Evaluation of a local-variable form, such as @scheme[(let ([x (+ 1
2)]) _expr)], is the same as for a procedure call. After @scheme[(+ 1
2)] produces a @tech{value}, it is stored in a fresh @tech{location}
that replaces every instance of @scheme[x] in @scheme[_expr].
@;------------------------------------------------------------------------
@section{Variables and Locations}
A @deftech{variable} is a placeholder for a @tech{value}, and an
expressions in an initial program refer to @tech{variables}. A
@deftech{top-level variable} is both a @tech{variable} and a
@tech{location}. Any other @tech{variable} is always replaced by a
@tech{location} at run-time, so that evaluation of expressions
involves only @tech{locations}. A single @deftech{local variable}
(i.e., a non-top-level, non-module-level @tech{variable}), such as a
procedure argument, can correspond to different @tech{locations}
through different instantiations.
For example, in the program
@schemeblock[(define y (+ (let ([x 5]) x) 6))]
both @scheme[y] and @scheme[x] are @tech{variables}. The @scheme[y]
@tech{variable} is a @tech{top-level variable}, and the @scheme[x] is
a @tech{local variable}. When this code is evaluated, a
@tech{location} is created for @scheme[x] to hold the value
@scheme[5], and a @tech{location} is also created for @scheme[y] to
hold the value @scheme[6].
The replacement of a @tech{variable} with a @tech{location} during
evaluation implements Scheme's @deftech{lexical scoping}. For example,
when a procedure-argument @tech{variable} @scheme[x] is replaced by
the @tech{location} @scheme[xloc], then it is replaced throughout the
body of the procedure, including with any nested @scheme[lambda]
forms. As a result, future references of the @tech{variable} always
access the same @tech{location}.
@;------------------------------------------------------------------------
@section[#:tag "module-eval-model"]{Modules and Module-Level Variables}
Most definitions in PLT Scheme are in modules. In terms of evaluation,
a module is essentially a prefix on a defined name, so that different
modules can define the name. That is, a @deftech{module-level
variable} is like a @tech{top-level variable} from the perspective of
evaluation.
One difference between a module an a top-level definition is that a
module can be declared without instantiating its module-level
definitions. Evaluation of a @scheme[require] @deftech{instantiates}
(i.e., triggers the @deftech{instantiation} of) a declared module,
which creates variables that correspond to its module-level
definitions.
For example, given the module declaration
@schemeblock[
(module m mzscheme
(define x 10))
]
the evaluation of @scheme[(require m)] creates the variable @scheme[x]
and installs @scheme[10] as its value. This @scheme[x] is unrelated to
any top-level definition of @scheme[x].
@;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@subsection[#:tag "module-phase"]{Module Phases}
A module can be @tech{instantiate}d in multiple @deftech{phases}. A
phase is an integer that, again, is effectively a prefix on the names
of module-level definitions. A top-level @scheme[require]
@tech{instantiates} a module at @tech{phase} 0, if the module is not
already @tech{instantiate}d at phase 0. A top-level
@scheme[require-for-syntax] @tech{instantiates} a module at
@tech{phase} 1 (if it is not already @tech{instantiate}d at that
level); a @scheme[require-for-syntax] also has a different binding
effect on further program parsing, as described in
@secref["intro-binding"].
Within a module, some definitions are shifted by a phase already; the
@scheme[define-for-syntax] form is like @scheme[define], but it
defines a variable at relative @tech{phase} 1, instead of relative
@tech{phase} 0. Thus, if the module is @tech{instantiate}d at phase 1,
the variables for @scheme[define-for-syntax] are created at phase 2,
and so on. Moreover, this relative phase acts as another layer of
prefixing, so that a @scheme[define] of @scheme[x] and a
@scheme[define-for-syntax] of @scheme[x] can co-exist in a module
without colliding. Again, the higher phases are mainly related to
program parsing, instead of normal evaluation.
If a module @tech{instantiate}d at @tech{phase} @math{n}
@scheme[require]s another module, then the @scheme[require]d module is
first @tech{instantiate}d at phase @math{n}, and so on
transitively. (Module @scheme[require]s cannot form cycles.) If a
module @tech{instantiate}d at phase @math{n}
@scheme[require-for-syntax]es another module, the other module is
first @tech{instantiate}d at @tech{phase} @math{n+1}, and so on. If a
module @tech{instantiate}d at phase @math{n} for non-zero @math{n}
@scheme[require-for-template]s another module, the other module is
first @tech{instantiate}d at @tech{phase} @math{n-1}, and so on.
A final distinction among module @tech{instantiations} is that
multiple @tech{instantiations} may exist at phase 1 and higher. These
@tech{instantiations} are created by the parsing of module forms (see
@secref["mod-parse"]), and are, again, conceptually distinguished
by prefixes.
@;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@subsection[#:tag "module-redeclare"]{Module Re-declarations}
@section-index["modules" "re-define"]
When a module is declared using a name for which a module is already
declared, the new declaration's definitions replace and extend the old
declarations. If a variable in the old declaration has no counterpart
in the new declaration, the old variable continues to exist, but its
binding is not included in the @tech{lexical information} for the
module body. If a new variable definition has a counterpart in the old
declaration, it effectively assigns to the old variable.
If a module is @tech{instantiate}d in any @tech{phase}s before it is
re-declared, each re-declaration of the module is immediately
@tech{instantiate}d in the same @tech{phase}s.
@;------------------------------------------------------------------------
@section[#:tag "mark-model"]{Continuation Frames and Marks}
Every continuation @scheme[_C] can be partitioned into
@deftech{continuation frames} @frame[1], @frame[2], ..., @frame["n"]
such that @scheme[_C] = @*sub[@frame[1] @*sub[@frame[2] @*sub["..."
@frame["n"]]]], and no frame @frame["i"] can be itself partitioned
into smaller continuations. Evaluation steps add and remove frames to
the current continuation, typically one at a time.
Each frame is conceptually annotated with a set of
@deftech{continuation marks}. A mark consists of a key and its value;
the key is an arbitrary value, and each frame includes at most one
mark for any key. Various operations set and extract marks from
continuations, so that marks can be used to attach information to a
dynamic extent. For example, marks can be used to record information
for a ``stack trace'' to be used when an exception is raised, or
to implement dynamic scope.
@;------------------------------------------------------------------------
@section[#:tag "prompt-model"]{Prompts, Delimited Continuations, and Barriers}
A @deftech{prompt} is a special kind of continuation frame that is
annotated with a specific @deftech{prompt-tag} (essentially a
continuation mark). Various operations allow the capture of frames in
the continuation from the redex position out to the nearest enclosing
prompt with a particular prompt tag; such a continuation is sometimes
called a @deftech{delimited continuation}. Other operations allow the
current continuation to be extended with a captured continuation
(specifically, a @deftech{composable continuation}). Yet other
operations abort the computation to the nearest enclosing prompt with
a particular tag, or replace the continuation to the nearest enclosing
prompt with another one. When a delimited continuation is captured,
the marks associated with the relevant frames are also captured.
A @deftech{continuation barrier} is another kind of continuation frame
that prohibits certain replacements of the current continuation with
another. Specifically, while an abort is allowed to remove a portion
of the continuation containing a prompt, the continuation can be
replaced by another only when the replacement also includes the
continuation barrier. Certain operations install barriers
automatically; in particular, when an exception handler is called, a
continuation barrier prohibits the continuation of the handler from
capturing the continuation past the exception point.
A @deftech{escape continuation} is essentially a derived concept. It
combines a prompt for escape purposes with a continuation for
mark-gathering purposes. as the name implies, escape continuations are
used only to abort to the point of capture, which means that
escape-continuation aborts can cross continuation barriers.
@;------------------------------------------------------------------------
@section[#:tag "thread-model"]{Threads}
Scheme supports multiple, pre-emptive @deftech{threads} of
evaluation. In terms of the evaluation model, this means that each
step in evaluation actually consists of multiple concurrent
expressions, rather than a single expression. The expressions all
share the same objects and top-level variables, so that they can
communicate through shared state. Most evaluation steps involve a
single step in a single expression, but certain synchronization
primitives require multiple threads to progress together in one step.
In addition to the state that is shared among all threads, each thread
has its own private state that is accessed through @deftech{thread
cells}. A thread cell is similar to a normal mutable object, but a
change to the value inside a thread cell is seen only when extracting
a value from the cell from the same thread. A thread cell can be
@deftech{preserved}; when a new thread is created, the creating
thread's value for a preserved thread cell serves as the initial value
for the cell in the created thread. For a non-preserved thread cell, a
new thread sees the same initial value (specified when the thread cell
is created) as all other threads.
@;------------------------------------------------------------------------
@section[#:tag "parameter-model"]{Parameters}
@deftech{Parameters} are essentially a derived concept in Scheme; they
are defined in terms of continuation marks and thread cells. However,
parameters are also built in, in the sense that some primitive
procedures consult parameter values. For example, the default output
stream for primitive output operations is determined by a parameter.
A parameter is a setting that is both thread-specific and
continuation-specific. In the empty continuation, each parameter
corresponds to a preserved thread cell; a corresponding
@deftech{parameter procedure} accesses and sets the thread cell's
value for the current thread.
In a non-empty continuation, a parameter's value is determined through
a @deftech{parameterization} that is associated with the nearest
enclosing continuation frame though a continuation mark (whose key is
not directly accessible). A parameterization maps each parameter to a
preserved thread cell, and the combination of thread cell and current
thread yields the parameter's value. A parameter procedure sets or
accesses the relevant thread cell for its parameter.
Various operations, such as @scheme[parameterize] or
@scheme[with-parameterization], install a parameterization into the
current continuation's frame.
@;------------------------------------------------------------------------
@section[#:tag "exn-model"]{Exceptions}
@deftech{Exceptions} are essentially a derived concept in Scheme; they
are defined in terms of continuations, prompts, and continuation
marks. However, exceptions are also built in, in the sense that
primitive forms and procedures may raise exceptions.
A handler for uncaught exceptions is designated through a built-in
parameter. A handler to catch exceptions can be associated with a
continuation frame though a continuation mark (whose key is not
directly accessible). When an exception is raised, the current
continuation's marks determine a chain of handler procedures that are
consulted to handle the exception.
One potential action of an exception handler is to abort the current
continuation up to an enclosing prompt with a particular tag. The
default handler for uncaught exceptions, in particular, aborts to a
particular tag for which a prompt is always present, because the
prompt is installed in the outermost frame of the continuation for any
new thread.
@;------------------------------------------------------------------------
@section[#:tag "custodian-model"]{Custodians}
A @deftech{custodian} manages a collection of threads, file-stream
ports, TCP ports, TCP listeners, UDP sockets, and byte converters.
Whenever a thread, file-stream port, TCP port, TCP listener, or UDP
socket is created, it is placed under the management of the
@deftech{current custodian} as determined by the
@scheme[current-custodian] @tech{parameter}.
@margin-note{In MrEd, custodians also manage eventspaces.}
Except for the root custodian, every @tech{custodian} itself it
managed by a @tech{custodian}, so that custodians form a hierarchy.
Every object managed by a subordinate custodian is also managed by the
custodian's owner.
When a @tech{custodian} is shut down via
@scheme[custodian-shutdown-all], it forcibly and immediately closes
the ports, TCP connections, etc. that it manages, as well as
terminating (or suspending) its threads. A custodian that has been
shut down cannot manage new objects. If the current custodian is shut
down before a procedure is called to create a managed resource (e.g.,
@scheme[open-input-port], @scheme[thread]), the
@exnraise[exn:fail:contract].
A thread can have multiple managing custodians, and a suspended thread
created with @scheme[thread/suspend-to-kill] can have zero
custodians. Extra custodians become associated with a thread through
@scheme[thread-resume] (see @secref["threadkill"]). When a thread
has multiple custodians, it is not necessarily killed by a
@scheme[custodian-shutdown-all], but shut-down custodians are removed
from the thread's managing set, and the thread is killed when its
managing set becomes empty.
The values managed by a custodian are only weakly held by the
custodian. As a result, a @tech{will} can be executed for a value that
is managed by a custodian. In addition, a custodian only weakly
references its subordinate custodians; if a subordinate custodian is
unreferenced but has its own subordinates, then the custodian may be
collected, at which point its subordinates become immediately
subordinate to the collected custodian's superordinate custodian.
In addition to the other entities managed by a custodian, a
@defterm{custodian box} created with @scheme[make-custodian-box]
strongly holds onto a value placed in the box until the box's
custodian is shut down. The custodian only weakly retains the box
itself, however (so the box and its content can be collected if there
are no other references to them).
When MzScheme is compiled with support for per-custodian memory
accounting (see @scheme[custodian-memory-accounting-available?]), the
@scheme[current-memory-use] procedure can report a custodian-specific
result. This result determines how much memory is occupied by objects
that are reachable from the custodian's managed values, especially its
threads, and including its sub-custodians' managed values. If an
object is reachable from two custodians where neither is an ancestor
of the other, an object is arbitrarily charged to one of the other,
and the choice can change after each collection; objects reachable
from both a custodian and its descendant, however, are reliably
charged to the descendant. Reachability for per-custodian accounting
does not include weak references, references to threads managed by
non-descendant custodians, references to non-descendant custodians, or
references to custodian boxes for non-descendant custodians.

View File

@ -251,11 +251,12 @@ case, the procedure result of @scheme[clause-transform-expr] is called
to transform the clause.
When @scheme[id] is used in any other expression position, the result
of @scheme[expr-transform-expr] is used; if it is an identifier
@scheme[_other-id], then any use of @scheme[id] is converted to a use
of @scheme[_other-id]; otherwise,@scheme[expr-transform-expr] must
produce a procedure that is used as a macro transformer.
of @scheme[expr-transform-expr] is used. If it is a procedure of zero
arguments, then the result must be an identifier @scheme[_other-id],
and any use of @scheme[id] is converted to a use of
@scheme[_other-id]. Otherwise,@scheme[expr-transform-expr] must
produce a procedure (of one argument) that is used as a macro
transformer.
When the @scheme[clause-transform-expr] transformer is used, it is
given a @scheme[_clause] as an argument, where the clause's form is
@ -298,10 +299,14 @@ spliced into the iteration essentially as follows:
]
where @scheme[_body-bindings] and @scheme[_done-expr] are from the
context of the @scheme[:do-in] use. The actual @scheme[loop] binding
and call has additional loop arguments to support iterations in
parallel with the @scheme[:do-in] form, and the other pieces are
similarly accompanied by pieces form parallel iterations.}
context of the @scheme[:do-in] use. The identifiers bound by the
@scheme[for] clause are typically part of the @scheme[([(inner-id ...)
inner-expr] ...)] section.
The actual @scheme[loop] binding and call has additional loop
arguments to support iterations in parallel with the @scheme[:do-in]
form, and the other pieces are similarly accompanied by pieces form
parallel iterations.}
@section{Do Loops}

View File

@ -0,0 +1,273 @@
#lang scribble/doc
@(require "mz.ss")
@title[#:tag "hashtables"]{Hash Tables}
A @deftech{hash table} (or simply @deftech{hash}) maps each of its
keys to a single value. For a given hash table, keys are equivalent
via @scheme[equal?] or @scheme[eq?], and keys are retained either
strongly or weakly (see @secref["weakbox"]). A hash table is also
either mutable or immutable; immutable tables support constant-time
functional update.
A hash table can be used as a two-valued @tech{sequence} (see
@secref["sequences"]). The keys and values of the hash table serve as
elements of the sequence (i.e., each element is a key and its
associated value). If a mapping is added to or removed from the hash
table during iteration, then an iteration step may fail with
@scheme[exn:fail:contract], or the iteration may skip or duplicate
keys and values. See also @scheme[in-hash], @scheme[in-hash-keys],
@scheme[in-hash-values], and @scheme[in-hash-pairs].
Two hash tables are @scheme[equal?] if they use the same
key-comparison procedure (@scheme[equal?] or @scheme[eq?]), if they
both hold keys strongly or weakly, and if they have the same
mutability.
@bold{Caveats concerning concurrent modification:} A mutable hash
table can be manipulated with @scheme[hash-ref], @scheme[hash-set!],
and @scheme[hash-remove!] concurrently by multiple threads, and the
operations are protected by a table-specific semaphore as needed. Two
caveats apply, however:
@itemize{
@item{If a thread is terminated while applying @scheme[hash-ref],
@scheme[hash-set!], or @scheme[hash-remove!] to a hash table that
uses @scheme[equal?] key comparisons, all current and future
operations on the hash table block indefinitely.}
@item{The @scheme[hash-map] and @scheme[hash-for-each] procedures do
not use the table's semaphore. Consequently, if a hash table is
extended with new keys by another thread while a map or for-each
traversal is in process, arbitrary key--value pairs can be dropped
or duplicated in the traversal. Similarly, if a map or for-each
procedure itself extends the table, arbitrary key--value pairs can
be dropped or duplicated. However, key mappings can be deleted or
remapped by any thread with no adverse affects (i.e., the change
does not affect a traversal if the key has been seen already,
otherwise the traversal skips a deleted key or uses the remapped
key's new value).}
}
@bold{Caveat concerning mutable keys:} If a key into an
@scheme[equal?]-based hash table is mutated (e.g., a key string is
modified with @scheme[string-set!]), then the hash table's behavior
for insertion and lookup operations becomes unpredictable.
@defproc[(hash? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a @tech{hash table}, @scheme[#f]
otherwise.}
@defproc[(hash-eq? [hash hash?]) boolean?]{
Returns @scheme[#t] if @scheme[hash] compares keys with @scheme[eq?],
@scheme[#f] if it compares with @scheme[equal?].}
@defproc[(hash-weak? [hash hash?]) boolean?]{
Returns @scheme[#t] if @scheme[hash] retains its keys weakly,
@scheme[#f] if it retains keys strongly.}
@defproc[(make-hash) hash?]{
Creates an empty mutable hash table that holds keys strongly and that
uses @scheme[equal?] to compare keys. See also
@scheme[make-custom-hash].}
@defproc[(make-hasheq) (and/c hash? hash-eq?)]{
Creates an empty mutable hash table that holds keys strongly and that
uses @scheme[eq?] to compare keys.}
@defproc[(make-weak-hash) (and/c hash? hash-weak?)]{
Creates an empty mutable hash table that holds keys weakly and that
uses @scheme[equal?] to compare keys. See also
@scheme[make-weak-custom-hash].}
@defproc[(make-weak-hasheq) (and/c hash? hash-eq? hash-weak?)]{
Creates an empty mutable hash table that holds keys weakly and that
uses @scheme[eq?] to compare keys.}
@defproc[(make-immutable-hash [assocs (listof pair?)])
(and/c hash? immutable?)]{
Creates an immutable hash table that compares keys with
@scheme[equal?]. In each element of @scheme[assocs], the @scheme[car]
of each pair is a key, and the @scheme[cdr] is the corresponding
value. The mappings are added to the table in the order that they
appear in @scheme[assocs], so later mappings can hide earlier
mappings.}
@defproc[(make-immutable-hasheq [assocs (listof pair?)])
(and/c hash? hash-eq? immutable?)]{
Like @scheme[make-immutable-hash], but the resulting hash table
compares keys with @scheme[eq?].}
@defproc[(hash-set! [hash (and/c hash? (not/c immutable?))]
[key any/c]
[v any/c]) void?]{
Maps @scheme[key] to @scheme[v] in @scheme[hash], overwriting
any existing mapping for @scheme[key].}
@defproc[(hash-set [hash (and/c hash? immutable?)]
[key any/c]
[v any/c])
(and/c hash? immutable?)]{
Functionally extends @scheme[hash] by mapping @scheme[key] to
@scheme[v], overwriting any existing mapping for @scheme[key], and
returning an extended hash table.}
@defproc[(hash-ref [hash hash?]
[key any/c]
[failure-result any/c (lambda () (raise (make-exn:fail ....)))])
any]{
Returns the value for @scheme[key] in @scheme[hash]. If no value
is found for @scheme[key], then @scheme[failure-result] determines the
result:
@itemize{
@item{If @scheme[failure-result] is a procedure, it is called
(through a tail call) with no arguments to produce the result.}
@item{Otherwise, @scheme[failure-result] is returned as the result.}
}}
@defproc[(hash-remove! [hash (and/c hash? (not/c immutable?))]
[key any/c])
void?]{
Removes any existing mapping for @scheme[key] in @scheme[hash].}
@defproc[(hash-remove [hash (and/c hash? immutable?)]
[key any/c])
(and/c hash? immutable?)]{
Functionally removes any existing mapping for @scheme[key] in
@scheme[hash], returning the updated hash table.}
@defproc[(hash-map [hash hash?]
[proc (any/c any/c . -> . any/c)])
(listof any/c)]{
Applies the procedure @scheme[proc] to each element in
@scheme[hash] in an unspecified order, accumulating the results
into a list. The procedure @scheme[proc] is called each time with a
key and its value. See the caveat above about concurrent
modification.}
@defproc[(hash-for-each [hash hash?]
[proc (any/c any/c . -> . any)])
void?]{
Applies @scheme[proc] to each element in @scheme[hash] (for the
side-effects of @scheme[proc]) in an unspecified order. The procedure
@scheme[proc] is called each time with a key and its value. See the
caveat above about concurrent modification.}
@defproc[(hash-count [hash hash?])
nonnegative-exact-integer?]{
Returns the number of keys mapped by @scheme[hash]. If
@scheme[hash] is not created with @scheme['weak], then the
result is computed in constant time and atomically. If
@scheme[hash] is created with @scheme['weak], see the caveat
above about concurrent modification.}
@defproc[(hash-iterate-first [hash hash?])
(or/c false/c nonnegative-exact-integer?)]{
Returns @scheme[#f] if @scheme[hash] contains no elements, otherwise
it returns an integer that is a index to the first element in the hash
table; ``first'' refers to an unspecified ordering of the table
elements, and the index values are not necessarily consecutive
integers. For a mutable @scheme[hash], this index is guaranteed to
refer to the first item only as long as no items are added to or
removed from @scheme[hash].}
@defproc[(hash-iterate-next [hash hash?]
[pos nonnegative-exact-integer?])
(or/c false/c nonnegative-exact-integer?)]{
Returns either an integer that is an index to the element in
@scheme[hash] after the element indexed by @scheme[pos] (which is not
necessarily one more than @scheme[pos]) or @scheme[#f] if @scheme[pos]
refers to the last element in @scheme[hash]. If @scheme[pos] is not a
valid index, then the @exnraise[exn:fail:contract]. For a mutable
@scheme[hash], the result index is guaranteed to refer to its item
only as long as no items are added to or removed from @scheme[hash].}
@defproc[(hash-iterate-key [hash hash?]
[pos nonnegative-exact-integer?])
any]{
Returns the key for the element in @scheme[hash] at index
@scheme[pos]. If @scheme[pos] is not a valid index for
@scheme[hash], the @exnraise[exn:fail:contract].}
@defproc[(hash-iterate-value [hash hash?]
[pos nonnegative-exact-integer?])
any]{
Returns the value for the element in @scheme[hash] at index
@scheme[pos]. If @scheme[pos] is not a valid index for
@scheme[hash], the @exnraise[exn:fail:contract].}
@defproc[(hash-copy [hash hash?])
(and/c hash? (not/c immutable?))]{
Returns a mutable hash table with the same mappings, same
key-comparison mode, and same key-holding strength as @scheme[hash].}
@defproc[(eq-hash-code [v any/c]) exact-integer?]{
Returns an exact integer; for any two @scheme[eq?] values, the
returned integer is the same. Furthermore, for the result integer
@scheme[k] and any other exact integer @scheme[j], @scheme[(= k j)]
implies @scheme[(eq? k j)].}
@defproc[(equal-hash-code [v any/c]) exact-integer?]{
Returns an exact integer; for any two @scheme[equal?] values, the
returned integer is the same. Furthermore, for the result integer
@scheme[k] and any other exact integer @scheme[j], @scheme[(= k j)]
implies @scheme[(eq? k j)]. A has code is computed even when
@scheme[v] contains a cycle through pairs, vectors, boxes, and/or
inspectable structure fields. See also @scheme[prop:equal+hash].}
@defproc[(equal-secondary-hash-code [v any/c]) exact-integer?]{
Like @scheme[equal-hash-code], but computes a secondary value suitable
for use in double hashing.}

View File

@ -1,772 +1,9 @@
#lang scribble/doc
@(require scribble/struct
(for-syntax scheme/base)
"mz.ss"
"prog-steps.ss")
@(require "mz.ss")
@(define reduces (make-element #f (list 'rarr)))
@(define rspace (make-element "ghost" (list 'rarr)))
@title[#:tag "model" #:style 'toc]{Language Model}
@(define *redex (lambda (c)
(make-element "highlighted" (list c))))
@(define-syntax redex
(syntax-rules () [(_ a) (*redex (scheme a))]))
@local-table-of-contents[]
@(define hole (make-element #f (list "[]")))
@(define (*sub c e) (make-element #f (list c "[" e "]")))
@(define langle (make-element 'tt (list "<")))
@(define rangle (make-element 'tt (list ">")))
@(define comma (make-element 'tt (list ", ")))
@(define-syntax sub
(syntax-rules () [(_ a b) (*sub (scheme a) (scheme b))]))
@(define (*state c e) (make-element #f (list langle c comma e rangle)))
@(define-syntax state
(syntax-rules () [(_ a b) (*state (scheme a) (scheme b))]))
@(define (frame n)
(make-element "schemevariable"
(list "C" (make-element 'subscript (list (format "~a" n))))))
@;------------------------------------------------------------------------
@title{Evaluation Model}
Scheme evaluation can be viewed as the simplification of expressions
to obtain values. For example, just as an elementary-school student
simplifies
@verbatim{ 1 + 1 = 2}
Scheme evaluation simplifies
@schemeblock[
(+ 1 1) #, @reduces 2
]
The arrow @reduces above replaces the more traditional @tt{=} to
emphasize that evaluation proceeds in a particular direction towards
simpler expressions. In particular, a @deftech{value} is an
expression that evaluation simplifies no further, such as the number
@scheme[2].
@;------------------------------------------------------------------------
@section[#:tag "cont-model"]{Sub-expression Evaluation and Continuations}
Some simplifications require more than one step. For example:
@schemeblock[
(- 4 #,(redex (+ 1 1))) #,reduces #,(redex (- 4 2)) #,reduces 2
]
An expression that is not a @tech{value} can always be partitioned
into two parts: a @deftech{redex}, which is the part that changed in a
single-step simplification (show in blue), and the
@deftech{continuation}, which is the surrounding expression
context. In @scheme[(- 4 (+ 1 1))], the redex is @scheme[(+ 1 1)], and
the continuation is @scheme[(- 4 #, @hole)], where @hole takes the
place of the redex. That is, the continuation says how to ``continue''
after the @tech{redex} is reduced to a @tech{value}.
Before some things can be evaluated, some sub-expressions must be
evaluated; for example, in the application @scheme[(- 4 (+ 1 1))], the
application of @scheme[-] cannot be reduced until the sub-expression
@scheme[(+ 1 1)] is reduced.
Thus, the specification of each syntactic form specifies how (some of)
its sub-expressions are evaluated, and then how the results are
combined to reduce the form away.
The @deftech{dynamic extent} of an expression is the sequence of
evaluation steps during which an expression contains the @tech{redex}.
@;------------------------------------------------------------------------
@section{Tail Position}
An expression @scheme[_expr1] is in @deftech{tail position} with
respect to an enclosing expression @scheme[_expr2] if, whenever
@scheme[_expr1] becomes a redex, its @tech{continuation} is the same
as was the enclosing @scheme[_expr2]'s @tech{continuation}.
For example, the @scheme[(+ 1 1)] expression is @italic{not} in @tech{tail
position} with respect to @scheme[(- 4 (+ 1 1))]. To illustrate, we use
the notation @sub[_C _expr] to mean the expression that is produced by
substituting @scheme[_expr] in place of @hole in the @tech{continuation}
@scheme[_C]:
@schemeblock[
#, @sub[_C (- 4 (+ 1 1))] #, @reduces #, @sub[_C (- 4 2)]
]
In this case, the @tech{continuation} for reducing @scheme[(+ 1 1)] is
@sub[_C (+ 4 #, @hole)], not just @scheme[_C].
In contrast, @scheme[(+ 1 1)] is in @tech{tail position} with respect
to @scheme[(if (zero? 0) (+ 1 1) 3)], because, for any continuation
@scheme[_C],
@schemeblock[
#, @sub[_C (if (zero? 0) (+ 1 1) 3)] #, @reduces #, @sub[_C (if #t (+ 1 1) 3)] #, @reduces #, @sub[_C (+ 1 1)]
]
The steps in this reduction sequence are driven by the definition of
@scheme[if], and they do not depend on the @tech{continuation}
@scheme[_C]. The ``then'' branch of an @scheme[if] form is always in
@tech{tail position} with respect to the @scheme[if] form. Due to a
similar reduction rule for @scheme[if] and @scheme[#f], the ``else''
branch of an @scheme[if] form is also in @tech{tail position}.
@tech{Tail-position} specifications provide a guarantee about the
asymptotic space consumption of a computation. In general, the
specification of @tech{tail positions} goes with each syntactic form,
like @scheme[if].
@;------------------------------------------------------------------------
@section[#:tag "values-model"]{Multiple Return Values}
A Scheme expression can evaluate to @deftech{multiple values}, in the
same way that a procedure can accept multiple arguments.
Most @tech{continuations} expect a particular number of result
@tech{values}. Indeed, most @tech{continuations}, such as @scheme[(+
#, @hole 1)] expect a single @tech{value}. The @tech{continuation}
@scheme[(let-values ([(x y) #, @hole]) _expr)] expects two result
@tech{values}; the first result replaces @scheme[x] in the body
@scheme[_expr], and the second replaces @scheme[y] in
@scheme[_expr]. The @tech{continuation} @scheme[(begin #, @hole (+ 1
2))] accepts any number of result @tech{values}, because it ignores
the result(s).
In general, the specification of a syntactic form inidicates the
number of @tech{values} that it produces and the number that it
expects from each of its sub-expression. In addtion, some procedures
(notably @scheme[values]) produce multiple @tech{values}, and some
procedures (notably @scheme[call-with-values]) create continuations
internally that accept a certain number of @tech{values}.
@;------------------------------------------------------------------------
@section{Top-Level Variables}
Given
@verbatim{ x = 10}
then an algebra student simplifies @tt{x + 1} as follows:
@verbatim{ x + 1 = 10 + 1 = 11}
Scheme works much the same way, in that a set of @tech{top-level
variables} are available for substitutions on demand during
evaluation. For example, given
@schemeblock[
(define x 10)
]
then
@schemeblock[
#,(redex (+ x 1)) #,reduces #,(redex (+ 10 1)) #,reduces 11
]
In Scheme, the way definitions appear is just as important as the way
that they are used. Scheme evaluation thus keeps track of both
definitions and the current expression, and it extends the set of
definitions in response to evaluating forms such as @scheme[define].
Each evaluation step, then, takes the current set of definitions and
program to a new set of definitions and program. Before a
@scheme[define] can be moved into the set of definitions, its
right-hand expression must be reduced to a @tech{value}.
@prog-steps/no-obj[
[{}
(begin (define x (code:hilite (+ 9 1))) (+ x 1))]
[{}
(begin (code:hilite (define x 10)) (+ x 1))]
[{(define x 10)}
(code:hilite (begin (void) (+ x 1)))]
[{(define x 10)}
(+ (code:hilite x) 1)]
[{(define x 10)}
(code:hilite (+ 10 1))]
[{(define x 10)}
11]
]
Using @scheme[set!], a program can change the value associated with an
existing @tech{top-level variable}:
@prog-steps/no-obj[
[{(define x 10)}
(begin (code:hilite (set! x 8)) x)]
[{(define x 8)}
(code:hilite (begin (void) x))]
[{(define x 8)}
(code:hilite x)]
[{(define x 8)}
8]
]
@;------------------------------------------------------------------------
@section{Objects and Imperative Update}
In addition to @scheme[set!] for imperative update of @tech{top-level
variables}, various procedures enable the modification of elements
within a compound data structure. For example, @scheme[vector-set!]
modifies the content of a vector.
To allow such modifications to data, we must distingiush between
@tech{values}, which are the results of expressions, and
@deftech{objects}, which hold the data referenced by a value.
A few kinds of @tech{objects} can serve directly as values, including
booleans, @scheme[(void)], and small exact integers. More generally,
however, a @tech{value} is a reference to an @tech{object}. For
example, a @tech{value} can be a reference to a particular vector that
currently holds the value @scheme[10] in its first slot. If an
@tech{object} is modified, then the modification is visible through
all copies of the @tech{value} that reference the same @tech{object}.
In the evaluation model, a set of @tech{objects} must be carried along
with each step in evaluation, just like the definition set. Operations
that create @tech{objects}, such as @scheme[vector], add to the set of
@tech{objects}:
@prog-steps[
[{}
{}
(begin (define x (code:hilite (vector 10 20)))
(define y x)
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{}
(begin (code:hilite (define x <o1>))
(define y x)
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(code:hilite (begin (void)
(define y x)
(vector-set! x 0 11)
(vector-ref y 0)))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(begin (define y (code:hilite x))
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)}
(begin (code:hilite (define y <o1>))
(vector-set! x 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (begin (void)
(vector-set! x 0 11)
(vector-ref y 0)))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(begin (vector-set! (code:hilite x) 0 11)
(vector-ref y 0))]
[{(define <o1> (vector 10 20))}
{(define x <o1>)
(define y <o1>)}
(begin (code:hilite (vector-set! <o1> 0 11))
(vector-ref y 0))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (begin (void)
(vector-ref y 0)))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(vector-ref (code:hilite y) 0)]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
(code:hilite (vector-ref <o1> 0))]
[{(define <o1> (vector 11 20))}
{(define x <o1>)
(define y <o1>)}
11]
]
The distinction between a @tech{top-level variable} and an object
reference is crucial. A @tech{top-level variable} is not a
@tech{value}; each time a @tech{variable} expression is evaluated, the
value is extracted from the current set of definitions. An object
reference, in contrast is a value, and therefore needs no further
evaluation. The model evaluation steps above use angle-bracketed
@scheme[<o1>] for an object reference to distinguish it from a
@tech{variable} name.
A direct object reference can never appear in a text-based source
program. A program representation created with
@scheme[datum->syntax-object], however, can embed direct references to
existing @tech{objects}.
@;------------------------------------------------------------------------
@section[#:tag "model-eq"]{Object Identity and Comparisons}
The @scheme[eq?] operator compares two @tech{values}, returning
@scheme[#t] when the values refer to the same @tech{object}. This form
of equality is suitable for comparing objects that support imperative
update (e.g., to determine that the effect of modifying an object
through one reference is visible through another reference). Also, an
@scheme[eq?] test evaluates quickly, and @scheme[eq?]-based hashing
is more lightweight than @scheme[equal?]-based hashing in hash tables.
In some cases, however, @scheme[eq?] is unsuitable as a comparison
operator, because the generation of @tech{objects} is not clearly
defined. In particular, two applications of @scheme[+] to the same two
exact integers may or may not produce results that are @scheme[eq?],
although the results are always @scheme[equal?]. Similarly, evaluation
of a @scheme[lambda] form typically generates a new procedure
@tech{object}, but it may re-use a procedure @tech{object} previously
generated by the same source @scheme[lambda] form.
The behavior of a datatype with respect to @scheme[eq?] is generally
specified with the datatype and its associated procedures.
@;------------------------------------------------------------------------
@section[#:tag "gc-model"]{Garbage Collection}
In the program state
@prog-steps[
[{(define <o1> (vector 10 20))
(define <o2> (vector 0))}
{(define x <o1>)}
(+ 1 x)]
]
evaluation cannot depend on @scheme[<o2>], because it is not part of
the program to evaluate, and it is not referenced by any definition
that is accessible in the program. The @tech{object} @scheme[<o2>] may
therefore be removed from the evaluation by @deftech{garbage
collection}.
A few special compound datatypes hold @deftech{weak references} to
objects. Such weak references are treated specially by the garbage
collector in determining which @tech{objects} are reachable for the
remainder of the computation. If an @tech{object} is reachable only
via a @tech{weak reference}, then the object can be reclaimed, and the
@tech{weak reference} is replaced by a different @tech{value}
(typically @scheme[#f]).
@;------------------------------------------------------------------------
@section{Procedure Applications and Local Variables}
Given
@verbatim{ f(x) = x + 10}
then an algebra student simplifies @tt{f(1)} as follows:
@verbatim{ f(7) = 7 + 10 = 17}
The key step in this simplification is take the body of the defined
function @tt{f}, and then replace each @tt{x} with the actual
@tech{value} @tt{1}.
Scheme procedure application works much the same way. A procedure is
an @tech{object}, so evaluating @scheme[(f 7)] starts with a
@tech{variable} lookup:
@prog-steps[
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
((code:hilite f) 7)]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
]
Unlike in algebra, however, the @tech{value} associated with an
argument can be changed in the body of a procedure by using
@scheme[set!], as in the example @scheme[(lambda (x) (begin (set! x 3)
x))]. Since the @tech{value} associated with @scheme[x] can be
changed, an actual value for cannot be substituted for @scheme[x] when
the procedure is applied.
Instead, a new @deftech{location} is created for each @tech{variable}
on each application. The argument @tech{value} is placed in the
@tech{location}, and each instance of the @tech{variable} in the
procedure body is replaced with the new @tech{location}:
@prog-steps[
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
(+ (code:hilite xloc) 10)]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
(code:hilite (+ 7 10))]
[{(define <p1> (lambda (x) (+ x 10)))}
{(define f <p1>)
(define xloc 7)}
17]
]
A @tech{location} is the same as a @tech{top-level variable}, but when
a @tech{location} is generated, it (conceptually) uses a name that has
not been used before and that cannot not be generated again or
accessed directly.
Generating a @tech{location} in this way means that @scheme[set!]
evaluates for @tech{local variables} in the same way as for
@tech{top-level variables}, because the @tech{local variable} is
always replaced with a @tech{location} by the time the @scheme[set!]
form is evaluated:
@prog-steps[
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)}
((code:hilite f) 7)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)}
(code:hilite (<p1> 7))]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 7)}
(begin (code:hilite (set! xloc 3)) xloc)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
(code:hilite (begin (void) xloc))]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
(code:hilite xloc)]
[{(define <p1> (lambda (x) (begin (set! x 3) x)))}
{(define f <p1>)
(define xloc 3)}
3]
]
The substition and @tech{location}-generation step of procedure
application requires that the argument is a @tech{value}. Therefore,
in @scheme[((lambda (x) (+ x 10)) (+ 1 2))], the @scheme[(+ 1 2)]
sub-expression must be simplified to the @tech{value} @scheme[3], and
then @scheme[3] can be placed into a @tech{location} for
@scheme[x]. In other words, Scheme is a @deftech{call-by-value}
language.
Evaluation of a local-variable form, such as @scheme[(let ([x (+ 1
2)]) _expr)], is the same as for a procedure call. After @scheme[(+ 1
2)] produces a @tech{value}, it is stored in a fresh @tech{location}
that replaces every instance of @scheme[x] in @scheme[_expr].
@;------------------------------------------------------------------------
@section{Variables and Locations}
A @deftech{variable} is a placeholder for a @tech{value}, and an
expressions in an initial program refer to @tech{variables}. A
@deftech{top-level variable} is both a @tech{variable} and a
@tech{location}. Any other @tech{variable} is always replaced by a
@tech{location} at run-time, so that evaluation of expressions
involves only @tech{locations}. A single @deftech{local variable}
(i.e., a non-top-level, non-module-level @tech{variable}), such as a
procedure argument, can correspond to different @tech{locations}
through different instantiations.
For example, in the program
@schemeblock[(define y (+ (let ([x 5]) x) 6))]
both @scheme[y] and @scheme[x] are @tech{variables}. The @scheme[y]
@tech{variable} is a @tech{top-level variable}, and the @scheme[x] is
a @tech{local variable}. When this code is evaluated, a
@tech{location} is created for @scheme[x] to hold the value
@scheme[5], and a @tech{location} is also created for @scheme[y] to
hold the value @scheme[6].
The replacement of a @tech{variable} with a @tech{location} during
evaluation implements Scheme's @deftech{lexical scoping}. For example,
when a procedure-argument @tech{variable} @scheme[x] is replaced by
the @tech{location} @scheme[xloc], then it is replaced throughout the
body of the procedure, including with any nested @scheme[lambda]
forms. As a result, future references of the @tech{variable} always
access the same @tech{location}.
@;------------------------------------------------------------------------
@section[#:tag "module-eval-model"]{Modules and Module-Level Variables}
Most definitions in PLT Scheme are in modules. In terms of evaluation,
a module is essentially a prefix on a defined name, so that different
modules can define the name. That is, a @deftech{module-level
variable} is like a @tech{top-level variable} from the perspective of
evaluation.
One difference between a module an a top-level definition is that a
module can be declared without instantiating its module-level
definitions. Evaluation of a @scheme[require] @deftech{instantiates}
(i.e., triggers the @deftech{instantiation} of) a declared module,
which creates variables that correspond to its module-level
definitions.
For example, given the module declaration
@schemeblock[
(module m mzscheme
(define x 10))
]
the evaluation of @scheme[(require m)] creates the variable @scheme[x]
and installs @scheme[10] as its value. This @scheme[x] is unrelated to
any top-level definition of @scheme[x].
@;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@subsection[#:tag "module-phase"]{Module Phases}
A module can be @tech{instantiate}d in multiple @deftech{phases}. A
phase is an integer that, again, is effectively a prefix on the names
of module-level definitions. A top-level @scheme[require]
@tech{instantiates} a module at @tech{phase} 0, if the module is not
already @tech{instantiate}d at phase 0. A top-level
@scheme[require-for-syntax] @tech{instantiates} a module at
@tech{phase} 1 (if it is not already @tech{instantiate}d at that
level); a @scheme[require-for-syntax] also has a different binding
effect on further program parsing, as described in
@secref["intro-binding"].
Within a module, some definitions are shifted by a phase already; the
@scheme[define-for-syntax] form is like @scheme[define], but it
defines a variable at relative @tech{phase} 1, instead of relative
@tech{phase} 0. Thus, if the module is @tech{instantiate}d at phase 1,
the variables for @scheme[define-for-syntax] are created at phase 2,
and so on. Moreover, this relative phase acts as another layer of
prefixing, so that a @scheme[define] of @scheme[x] and a
@scheme[define-for-syntax] of @scheme[x] can co-exist in a module
without colliding. Again, the higher phases are mainly related to
program parsing, instead of normal evaluation.
If a module @tech{instantiate}d at @tech{phase} @math{n}
@scheme[require]s another module, then the @scheme[require]d module is
first @tech{instantiate}d at phase @math{n}, and so on
transitively. (Module @scheme[require]s cannot form cycles.) If a
module @tech{instantiate}d at phase @math{n}
@scheme[require-for-syntax]es another module, the other module is
first @tech{instantiate}d at @tech{phase} @math{n+1}, and so on. If a
module @tech{instantiate}d at phase @math{n} for non-zero @math{n}
@scheme[require-for-template]s another module, the other module is
first @tech{instantiate}d at @tech{phase} @math{n-1}, and so on.
A final distinction among module @tech{instantiations} is that
multiple @tech{instantiations} may exist at phase 1 and higher. These
@tech{instantiations} are created by the parsing of module forms (see
@secref["mod-parse"]), and are, again, conceptually distinguished
by prefixes.
@;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@subsection[#:tag "module-redeclare"]{Module Re-declarations}
@section-index["modules" "re-define"]
When a module is declared using a name for which a module is already
declared, the new declaration's definitions replace and extend the old
declarations. If a variable in the old declaration has no counterpart
in the new declaration, the old variable continues to exist, but its
binding is not included in the @tech{lexical information} for the
module body. If a new variable definition has a counterpart in the old
declaration, it effectively assigns to the old variable.
If a module is @tech{instantiate}d in any @tech{phase}s before it is
re-declared, each re-declaration of the module is immediately
@tech{instantiate}d in the same @tech{phase}s.
@;------------------------------------------------------------------------
@section[#:tag "mark-model"]{Continuation Frames and Marks}
Every continuation @scheme[_C] can be partitioned into
@deftech{continuation frames} @frame[1], @frame[2], ..., @frame["n"]
such that @scheme[_C] = @*sub[@frame[1] @*sub[@frame[2] @*sub["..."
@frame["n"]]]], and no frame @frame["i"] can be itself partitioned
into smaller continuations. Evaluation steps add and remove frames to
the current continuation, typically one at a time.
Each frame is conceptually annotated with a set of
@deftech{continuation marks}. A mark consists of a key and its value;
the key is an arbitrary value, and each frame includes at most one
mark for any key. Various operations set and extract marks from
continuations, so that marks can be used to attach information to a
dynamic extent. For example, marks can be used to record information
for a ``stack trace'' to be used when an exception is raised, or
to implement dynamic scope.
@;------------------------------------------------------------------------
@section[#:tag "prompt-model"]{Prompts, Delimited Continuations, and Barriers}
A @deftech{prompt} is a special kind of continuation frame that is
annotated with a specific @deftech{prompt-tag} (essentially a
continuation mark). Various operations allow the capture of frames in
the continuation from the redex position out to the nearest enclosing
prompt with a particular prompt tag; such a continuation is sometimes
called a @deftech{delimited continuation}. Other operations allow the
current continuation to be extended with a captured continuation
(specifically, a @deftech{composable continuation}). Yet other
operations abort the computation to the nearest enclosing prompt with
a particular tag, or replace the continuation to the nearest enclosing
prompt with another one. When a delimited continuation is captured,
the marks associated with the relevant frames are also captured.
A @deftech{continuation barrier} is another kind of continuation frame
that prohibits certain replacements of the current continuation with
another. Specifically, while an abort is allowed to remove a portion
of the continuation containing a prompt, the continuation can be
replaced by another only when the replacement also includes the
continuation barrier. Certain operations install barriers
automatically; in particular, when an exception handler is called, a
continuation barrier prohibits the continuation of the handler from
capturing the continuation past the exception point.
A @deftech{escape continuation} is essentially a derived concept. It
combines a prompt for escape purposes with a continuation for
mark-gathering purposes. as the name implies, escape continuations are
used only to abort to the point of capture, which means that
escape-continuation aborts can cross continuation barriers.
@;------------------------------------------------------------------------
@section[#:tag "thread-model"]{Threads}
Scheme supports multiple, pre-emptive @deftech{threads} of
evaluation. In terms of the evaluation model, this means that each
step in evaluation actually consists of multiple concurrent
expressions, rather than a single expression. The expressions all
share the same objects and top-level variables, so that they can
communicate through shared state. Most evaluation steps involve a
single step in a single expression, but certain synchronization
primitives require multiple threads to progress together in one step.
In addition to the state that is shared among all threads, each thread
has its own private state that is accessed through @deftech{thread
cells}. A thread cell is similar to a normal mutable object, but a
change to the value inside a thread cell is seen only when extracting
a value from the cell from the same thread. A thread cell can be
@deftech{preserved}; when a new thread is created, the creating
thread's value for a preserved thread cell serves as the initial value
for the cell in the created thread. For a non-preserved thread cell, a
new thread sees the same initial value (specified when the thread cell
is created) as all other threads.
@;------------------------------------------------------------------------
@section[#:tag "parameter-model"]{Parameters}
@deftech{Parameters} are essentially a derived concept in Scheme; they
are defined in terms of continuation marks and thread cells. However,
parameters are also built in, in the sense that some primitive
procedures consult parameter values. For example, the default output
stream for primitive output operations is determined by a parameter.
A parameter is a setting that is both thread-specific and
continuation-specific. In the empty continuation, each parameter
corresponds to a preserved thread cell; a corresponding
@deftech{parameter procedure} accesses and sets the thread cell's
value for the current thread.
In a non-empty continuation, a parameter's value is determined through
a @deftech{parameterization} that is associated with the nearest
enclosing continuation frame though a continuation mark (whose key is
not directly accessible). A parameterization maps each parameter to a
preserved thread cell, and the combination of thread cell and current
thread yields the parameter's value. A parameter procedure sets or
accesses the relevant thread cell for its parameter.
Various operations, such as @scheme[parameterize] or
@scheme[with-parameterization], install a parameterization into the
current continuation's frame.
@;------------------------------------------------------------------------
@section[#:tag "exn-model"]{Exceptions}
@deftech{Exceptions} are essentially a derived concept in Scheme; they
are defined in terms of continuations, prompts, and continuation
marks. However, exceptions are also built in, in the sense that
primitive forms and procedures may raise exceptions.
A handler for uncaught exceptions is designated through a built-in
parameter. A handler to catch exceptions can be associated with a
continuation frame though a continuation mark (whose key is not
directly accessible). When an exception is raised, the current
continuation's marks determine a chain of handler procedures that are
consulted to handle the exception.
One potential action of an exception handler is to abort the current
continuation up to an enclosing prompt with a particular tag. The
default handler for uncaught exceptions, in particular, aborts to a
particular tag for which a prompt is always present, because the
prompt is installed in the outermost frame of the continuation for any
new thread.
@;------------------------------------------------------------------------
@section[#:tag "custodian-model"]{Custodians}
A @deftech{custodian} manages a collection of threads, file-stream
ports, TCP ports, TCP listeners, UDP sockets, and byte converters.
Whenever a thread, file-stream port, TCP port, TCP listener, or UDP
socket is created, it is placed under the management of the
@deftech{current custodian} as determined by the
@scheme[current-custodian] @tech{parameter}.
@margin-note{In MrEd, custodians also manage eventspaces.}
Except for the root custodian, every @tech{custodian} itself it
managed by a @tech{custodian}, so that custodians form a hierarchy.
Every object managed by a subordinate custodian is also managed by the
custodian's owner.
When a @tech{custodian} is shut down via
@scheme[custodian-shutdown-all], it forcibly and immediately closes
the ports, TCP connections, etc. that it manages, as well as
terminating (or suspending) its threads. A custodian that has been
shut down cannot manage new objects. If the current custodian is shut
down before a procedure is called to create a managed resource (e.g.,
@scheme[open-input-port], @scheme[thread]), the
@exnraise[exn:fail:contract].
A thread can have multiple managing custodians, and a suspended thread
created with @scheme[thread/suspend-to-kill] can have zero
custodians. Extra custodians become associated with a thread through
@scheme[thread-resume] (see @secref["threadkill"]). When a thread
has multiple custodians, it is not necessarily killed by a
@scheme[custodian-shutdown-all], but shut-down custodians are removed
from the thread's managing set, and the thread is killed when its
managing set becomes empty.
The values managed by a custodian are only weakly held by the
custodian. As a result, a @tech{will} can be executed for a value that
is managed by a custodian. In addition, a custodian only weakly
references its subordinate custodians; if a subordinate custodian is
unreferenced but has its own subordinates, then the custodian may be
collected, at which point its subordinates become immediately
subordinate to the collected custodian's superordinate custodian.
In addition to the other entities managed by a custodian, a
@defterm{custodian box} created with @scheme[make-custodian-box]
strongly holds onto a value placed in the box until the box's
custodian is shut down. The custodian only weakly retains the box
itself, however (so the box and its content can be collected if there
are no other references to them).
When MzScheme is compiled with support for per-custodian memory
accounting (see @scheme[custodian-memory-accounting-available?]), the
@scheme[current-memory-use] procedure can report a custodian-specific
result. This result determines how much memory is occupied by objects
that are reachable from the custodian's managed values, especially its
threads, and including its sub-custodians' managed values. If an
object is reachable from two custodians where neither is an ancestor
of the other, an object is arbitrarily charged to one of the other,
and the choice can change after each collection; objects reachable
from both a custodian and its descendant, however, are reliably
charged to the descendant. Reachability for per-custodian accounting
does not include weak references, references to threads managed by
non-descendant custodians, references to non-descendant custodians, or
references to custodian boxes for non-descendant custodians.
@include-section["eval-model.scrbl"]
@include-section["syntax-model.scrbl"]

View File

@ -27,7 +27,6 @@ languages, where @schememodname[scheme] includes all of
@table-of-contents[]
@include-section["model.scrbl"]
@include-section["syntax-model.scrbl"]
@include-section["syntax.scrbl"]
@include-section["data.scrbl"]
@include-section["struct.scrbl"]

View File

@ -7,7 +7,7 @@
@local-table-of-contents[]
A @pidefterm{string} is a fixed-length array of
A @deftech{string} is a fixed-length array of
@seclink["characters"]{characters}.
@index['("strings" "immutable")]{A} string can be @defterm{mutable} or

View File

@ -8,10 +8,10 @@
@guideintro["define-struct"]{structure types via @scheme[define-struct]}
A @deftech{structure type} is a record datatype composing a number
of @idefterm{fields}. A @pidefterm{structure}, an instance of a
structure type, is a first-class value that contains a value for each
field of the structure type. A structure instance is created with a
A @deftech{structure type} is a record datatype composing a number of
@idefterm{fields}. A @deftech{structure}, an instance of a structure
type, is a first-class value that contains a value for each field of
the structure type. A structure instance is created with a
type-specific @tech{constructor} procedure, and its field values are
accessed and changed with type-specific @tech{accessor} and
@tech{mutator} procedures. In addition, each structure type has a

View File

@ -0,0 +1,145 @@
#lang scribble/doc
@(require "mz.ss")
@title[#:tag "vectors"]{Vectors}
A @deftech{vector} is a fixed-length array with constant-time access
and update of the vector slots, which are numbered from @scheme[0] to
one less than the number of slots in the vector.
Two vectors are @scheme[equal?] if they have the same length, and if
the values in corresponding slots of the the vectors are
@scheme[equal?].
A vector can be @defterm{mutable} or @defterm{immutable}. When an
immutable vector is provided to a procedure like @scheme[vector-set!],
the @exnraise[exn:fail:contract]. Vectors generated by the default
reader (see @secref["parse-string"]) are immutable.
A vector can be used as a single-valued sequence (see
@secref["sequences"]). The elements of the vector serve as elements
of the sequence. See also @scheme[in-vector].
@defproc[(vector? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a vector, @scheme[#f] otherwise.}
@defproc[(make-vector [size nonnegative-exact-integer?]
[v any/c 0]) vector?]{
Returns a mutable vector with @scheme[size] slots, where all slots are
initialized to contain @scheme[v].}
@defproc[(vector [v any/c] ...) vector?]{
Returns a mutable vector with as many slots as provided @scheme[v]s,
where the slots are initialized to contain the given @scheme[v]s in
order.}
@defproc[(vector-immutable [v any/c] ...) (and/c vector?
immutable?)]{
Returns an immutable vector with as many slots as provided
@scheme[v]s, where the slots are contain the given @scheme[v]s in
order.}
@defproc[(vector-length [vec vector?]) nonnegative-exact-integer?]{
Returns the length of @scheme[vec] (i.e., the number of slots in the
vector).}
@defproc[(vector-ref [vec vector?][pos nonnegative-exact-integer?]) any/c]{
Returns the element in slot @scheme[pos] of @scheme[vec]. The first
slot is position @scheme[0], and the last slot is one less than
@scheme[(vector-length vec)].}
@defproc[(vector-set! [vec (and/c vector? (not/c immutable?))]
[pos nonnegative-exact-integer?]
[v any/c])
void?]{
Updates the slot @scheme[pos] of @scheme[vec] to contain @scheme[v].}
@defproc[(vector->list [vec vector?])
list?]{
Returns a list with the same length and elements as @scheme[vec].}
@defproc[(list->vector [lst list?])
vector?]{
Returns a mutable vector with the same length and elements as
@scheme[lst].}
@defproc[(vector->immutable-vector [vec vector?])
(and/c vector? immutable?)]{
Returns an immutable vector with the same length and elements as @scheme[vec].
If @scheme[vec] is itself immutable, then it is returned as the result.}
@defproc[(vector-fill! [vec (and/c vector? (not/c immutable?))]
[v any/c])
void?]{
Changes all slots of @scheme[vec] to contain @scheme[v].}
@defproc[(vector-copy! [dest (and/c vector? (not/c immutable?))]
[dest-start exact-nonnegative-integer?]
[src vector?]
[src-start exact-nonnegative-integer? 0]
[src-end exact-nonnegative-integer? (vector-length src)])
void?]{
Changes the elements of @scheme[dest] starting at position
@scheme[dest-start] to match the elements in @scheme[src] from
@scheme[src-start] (inclusive) to @scheme[src-end] (exclusive). The
vectors @scheme[dest] and @scheme[src] can be the same vector, and in
that case the destination region can overlap with the source region;
the destination elements after the copy match the source elements
from before the copy. If any of @scheme[dest-start],
@scheme[src-start], or @scheme[src-end] are out of range (taking into
account the sizes of the vectors and the source and destination
regions), the @exnraise[exn:fail:contract].
@examples[(define v (vector 'A 'p 'p 'l 'e))
(vector-copy! v 4 #(y))
(vector-copy! v 0 v 3 4)
v]}
@defproc[(vector->values [vec vector?]
[start-pos nonnegative-exact-integer? 0]
[end-pos nonnegative-exact-integer? (vector-length vec)])
any]{
Returns @math{@scheme[end-pos] - @scheme[start-pos]} values, which are
the elements of @scheme[vec] from @scheme[start-pos] (inclusive) to
@scheme[end-pos] (exclusive). If @scheme[start-pos] or
@scheme[end-pos] are greater than @scheme[(vector-length vec)], or if
@scheme[end-pos] is less than @scheme[start-pos], the
@exnraise[exn:fail:contract].}
@defproc[(build-vector [n exact-nonnegative-integer?]
[proc (exact-nonnegative-integer? . -> . any.c)])
vector?]{
Creates a vector of @scheme[n] elements by applying @scheme[proc] to
the integers from @scheme[0] to @scheme[(sub1 n)] in order. If
@scheme[_vec] is the resulting vector, then @scheme[(vector-ref _vec
_i)] is the value produced by @scheme[(proc _i)].
@examples[
(build-vector 5 add1)
]}

View File

@ -0,0 +1,109 @@
(load-relative "loadtest.ss")
(Section 'dict)
(require scheme/dict)
;; Currently relying on the `map' an `for-each' to test `dict-iterate-...',
;; and custom hashes to test `prop:dict' use.
(define (try-simple d mutable? can-remove? can-update? [orig-one 1])
(test #t dict? d)
(test 'one dict-ref d 1)
(test 'nope dict-ref d 100 'nope)
(test 'nope dict-ref d 100 (lambda () 'nope))
(test #t ormap values (dict-map d (lambda (k v) (equal? k orig-one))))
(test #t ormap values (dict-map d (lambda (k v) (equal? v 'one))))
(test #f ormap values (dict-map d (lambda (k v) (equal? k 100))))
(test mutable? dict-mutable? d)
(test can-remove? dict-can-remove-keys? d)
(test can-update? dict-can-functional-set? d)
(test (dict-map d cons) 'in-dict
(for/list ([(k v) (in-dict d)])
(cons k v)))
(test (dict-map d cons) 'in-dict/keys/vals
(for/list ([k (in-dict-keys d)]
[v (in-dict-values d)])
(cons k v)))
(test (dict-map d cons) 'in-dict-pairs
(for/list ([p (in-dict-pairs d)])
p))
(let ([l null])
(dict-for-each d (lambda (k v) (set! l (cons (cons k v) l))))
(test (reverse l) dict-map d cons)
(test (length l) dict-count d))
(if (not can-remove?)
(begin
(err/rt-test (dict-remove! d 1))
(err/rt-test (dict-remove d 1))
(err/rt-test (dict-set d 1 "ONE"))
(test (void) dict-set! d 1 "ONE")
(test "ONE" dict-ref d 1))
(let ([cnt (dict-count d)]
[smaller (if mutable?
(begin
(err/rt-test (dict-remove d 1))
(dict-remove! d 1)
d)
(begin
(err/rt-test (dict-remove! d 1))
(dict-remove d 1)))])
(test 'nope dict-ref smaller 1 'nope)
(test (sub1 cnt) dict-count smaller)
(let ([try-add
(lambda (d val)
(let ([bigger (if mutable?
(begin
(err/rt-test (dict-set smaller 1 val))
(dict-set! smaller 1 val)
d)
(begin
(err/rt-test (dict-set! smaller 1 val))
(dict-set smaller 1 val)))])
(test cnt dict-count bigger)
(when (eq? val 'one)
(unless (pair? d)
(test #t equal? d bigger)))))])
(try-add smaller "ONE")
(try-add d "ONE")
(try-add d 'one)))))
(try-simple (vector 'zero 'one 'two) #t #f #f)
(try-simple #hash((1 . one) (#f . 7)) #f #t #t)
(try-simple #hasheq((1 . one) (#f . 7)) #f #t #t)
(try-simple (hash-copy #hash((1 . one) (#f . 7))) #t #t #f)
(try-simple (hash-copy #hasheq((1 . one) (#f . 7))) #t #t #f)
(try-simple '((0 . zero) (1 . one)) #f #t #t)
(try-simple '((1 . one) (0 . zero)) #f #t #t)
(try-simple (let ([h (make-custom-hash (lambda (a b)
(string=? (format "~a" a)
(format "~a" b)))
(lambda (a)
(equal-hash-code (format "~a" a))))])
(dict-set! h "1" 'one)
(dict-set! h "2" 'two)
h)
#t #t #f
"1")
(try-simple (let* ([h (make-immutable-custom-hash
(lambda (a b)
(string=? (format "~a" a)
(format "~a" b)))
(lambda (a)
(equal-hash-code (format "~a" a))))]
[h (dict-set h "1" 'one)]
[h (dict-set h "2" 'two)])
h)
#f #t #t
"1")
;; ----------------------------------------
(report-errs)

View File

@ -1674,7 +1674,7 @@ static Scheme_Object *hash_table_put(int argc, Scheme_Object *argv[])
Scheme_Object *v = argv[0];
if (!SCHEME_HASHTRP(v)) {
scheme_wrong_type("hash-set", "immutable table", 0, argc, argv);
scheme_wrong_type("hash-set", "immutable hash", 0, argc, argv);
return NULL;
}
@ -1750,7 +1750,7 @@ static Scheme_Object *hash_table_remove_bang(int argc, Scheme_Object *argv[])
static Scheme_Object *hash_table_remove(int argc, Scheme_Object *argv[])
{
if (!SCHEME_HASHTRP(argv[0]))
scheme_wrong_type("hash-remove", "immutable table", 0, argc, argv);
scheme_wrong_type("hash-remove", "immutable hash", 0, argc, argv);
return (Scheme_Object *)scheme_hash_tree_set((Scheme_Hash_Tree *)argv[0], argv[1], NULL);
}
@ -2173,7 +2173,7 @@ static Scheme_Object *do_make_hash_placeholder(const char *who, int eq, int argc
ph = scheme_alloc_object();
ph->type = scheme_table_placeholder_type;
SCHEME_IPTR_VAL(ph) = l;
SCHEME_IPTR_VAL(ph) = argv[0];
SCHEME_PINT_VAL(ph) = eq;
return ph;