#lang scribble/doc
@(require "mz.ss")

@(define-syntax op
  (syntax-rules ()
    [(_ (x ...)) (x ...)]
    [(_ id) @scheme[id]]))
@(define-syntax-rule (operations i ...)
   (itemlist #:style 'compact @item{@op[i]} ...))

@title[#:tag "chaperones"]{Proxies and Chaperones}

A @deftech{proxy} is a wrapper for a value where the wrapper
redirects certain of the value's operations. Proxies apply only to procedures,
@tech{structures} for which an accessor or mutator is available,
@tech{structure types}, @tech{hash tables}, @tech{vectors},
and @tech{box}es. A proxied value is @scheme[equal?] to the original
value, but not @scheme[eq?] to the original value.

A @deftech{chaperone} is a kind of proxy whose refinement of a value's
operation is restricted to side effects (including, in particular,
raising an exception) or chaperoning values supplied to or produced by
the operation. For example, a vector chaperone can redirect
@scheme[vector-ref] to raise an exception if the accessed vector slot
contains a string, or it can cause the result of @scheme[vector-ref]
to be a chaperoned variant of the value that is in the accessed vector
slot, but it cannot redirect @scheme[vector-ref] to produce a value
that is arbitrarily different from the value in the vector slot.

A non-@tech{chaperone} @tech{proxy}, in contrast, can refine an operation to swap one
value for any another. A proxy cannot be applied to an immutable value
or refine the access to an immutable field in an instance of a @tech{structure
type}, since arbitrary replacement of an operation's value amounts to
mutation of the proxied value.

Beware that each of the following operations can be redirected to
arbitrary procedure through proxies on the operation's
argument---assuming that the operation is available to the creator of
the proxy:

@operations[@t{a structure-field accesor}
            @t{a structure-field mutator}
            @t{a structure type property accessor}
            @t{application of a procedure}
            unbox set-box!
            vector-ref vector-set!
            hash-ref hash-set hash-set! hash-remove hash-remove!]

Derived operations, such as printing a value, can be redirected
through proxies due to their use of accessor functions. The
@scheme[equal?], @scheme[equal-hash-code], and
@scheme[equal-secondary-hash-code] operations, in contrast, may bypass
proxies (but they are not obliged to).

In addition to redirecting operations that work on a value, a
proxy can include @deftech{proxy properties} for a proxied
value. A @tech{proxy property} is similar to a @tech{structure
type property}, but it applies to chaperones instead of structure
types and their instances.


@defproc[(proxy? [v any/c]) boolean?]{

Returns @scheme[#t] if @scheme[v] is a proxy, @scheme[#f] otherwise.

Programs and libraries generally should avoid @scheme[proxy?] and
treat proxies the same as unproxied values. In rare cases,
@scheme[proxy?] may be needed to guard against redirection by a
proxy of an operation to an arbitrary procedure.}


@defproc[(chaperone? [v any/c]) boolean?]{

Returns @scheme[#t] if @scheme[v] is a chaperone, @scheme[#f] otherwise.

Programs and libraries generally should avoid @scheme[chaperone?] for
the same reason that they should avoid @racket[proxy?].}


@defproc[(proxy-of? [v1 any/c] [v2 any/c]) boolean?]{

Indicates whether @scheme[v1] can be considered equivalent modulo
proxies to @scheme[v2].

For values that include no proxies, @scheme[v1] and @scheme[v2] can
be considered proxies of each other if they are @scheme[equal?].

Otherwise, all proxies of @scheme[v2] must be intact in @scheme[v1],
in the sense that parts of @scheme[v2] must be derived from
@scheme[v1] through one of the proxy constructors (e.g.,
@scheme[proxy-procedure] or @racket[chaperone-procedure]).}


@defproc[(chaperone-of? [v1 any/c] [v2 any/c]) boolean?]{

Indicates whether @scheme[v1] can be considered equivalent modulo
chaperones to @scheme[v2].

For values that include no chaperones, @scheme[v1] and @scheme[v2] can
be considered chaperones of each other if they are @scheme[equal?],
except that the mutability of vectors and boxes with @scheme[v1] and
@scheme[v2] must be the same.

Otherwise, all chaperones of @scheme[v2] must be intact in
@scheme[v1], in the sense that parts of @scheme[v2] must be derived
from @scheme[v1] through one of the chaperone constructors (e.g.,
@scheme[chaperone-procedure]).}

@; ------------------------------------------------------------
@section{Proxy Constructors}

@defproc[(proxy-procedure [proc procedure?]
                          [wrapper-proc procedure?]
                          [prop proxy-property?]
                          [prop-val any] ... ...)
         (and/c procedure? proxy?)]{

Returns a proxied procedure that has the same arity, name, and
other attributes as @scheme[proc]. When the proxied procedure is
applied, the arguments are first passed to @scheme[wrapper-proc], and
then the results from @scheme[wrapper-proc] are passed to
@scheme[proc]. The @scheme[wrapper-proc] can also supply a procedure
that processes the results of @scheme[proc].

The arity of @scheme[wrapper-proc] must include the arity of
@scheme[proc]. The allowed keyword arguments of @scheme[wrapper-proc]
must be a superset of the allowed keywords of @scheme[proc]. The
required keyword arguments of @scheme[wrapper-proc] must be a subset
of the required keywords of @scheme[proc].

For applications without keywords, the result of @scheme[wrapper-proc]
must be either the same number of values as supplied to it or one more
than the number of supplied values, where an extra result is supplied
before the others. The additional result, if any, must be a procedure
that accepts as many results as produced by @scheme[proc]; it must
return the same number of results.  If @scheme[wrapper-proc] returns
the same number of values as it is given (i.e., it does not return a
procedure to proxy @scheme[proc]'s result), then @scheme[proc] is
called in @tech{tail position} with respect to the call to the proxy.

For applications that include keyword arguments, @scheme[wrapper-proc]
must return an additional value before any other values but after the
result-proxying procedure (if any). The additional value must be a
list of proxys of the keyword arguments that were supplied to the
proxied procedure (i.e., not counting optional arguments that were
not supplied). The arguments must be ordered according to the sorted
order of the supplied arguments' keywords.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[procedure-proxy] must be even) add proxy properties
or override proxy-property values of @scheme[proc].}

@defproc[(proxy-struct [v any/c]
                       [orig-proc (or/c struct-accessor-procedure?
                                        struct-mutator-procedure?)]
                       [redirect-proc procedure?] ... ...
                       [prop proxy-property?]
                       [prop-val any] ... ...)
          any/c]{

Returns a proxied value like @scheme[v], but with certain
operations on the proxied redirected. The @scheme[orig-proc]s
indicate the operations to redirect, and the corresponding
@scheme[redirect-proc]s supply the redirections.

The protocol for a @scheme[redirect-proc] depends on the corresponding
@scheme[orig-proc]:

@itemlist[

 @item{A structure-field: @scheme[redirect-proc]
      must accept two arguments, @scheme[v] and the value
      @scheme[_field-v] that @scheme[orig-proc] produces for
      @scheme[v]; it must return a replacement for
      @scheme[_field-v]. The corresponding field must not be
      immutable.}

 @item{A structure field mutator: @scheme[redirect-proc] must accept
      two arguments, @scheme[v] and the value @scheme[_field-v]
      supplied to the mutator; it must return a replacement for
      @scheme[_field-v] to be propagated to @scheme[orig-proc] and
      @scheme[v].}

]

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[proxy-procedure] must be odd) add proxy properties
or override proxy-property values of @scheme[v].}

@defproc[(proxy-vector [vec (and/c vector? (not/c immutable?))]
                       [ref-proc (vector? exact-nonnegative-integer? any/c . -> . any/c)]
                       [set-proc (vector? exact-nonnegative-integer? any/c . -> . any/c)]
                       [prop proxy-property?]
                       [prop-val any] ... ...)
          (and/c vector? proxy?)]{

Returns a proxied value like @scheme[vec], but with
@scheme[vector-ref] and @scheme[vector-set!] operations on the
proxied vector redirected.

The @scheme[ref-proc] must accept @scheme[vec], an index passed to
@scheme[vector-ref], and the value that @scheme[vector-ref] on
@scheme[vec] produces for the given index; it must produce a
replacement for the value, which is the result of @scheme[vector-ref]
on the proxy.

The @scheme[set-proc] must accept @scheme[vec], an index passed to
@scheme[vector-set!], and the value passed to @scheme[vector-set!]; it
must produce a replacement for the value, which is used
with @scheme[vector-set!] on the original @scheme[vec] to install the
value.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[proxy-vector] must be odd) add proxy properties
or override proxy-property values of @scheme[vec].}

@defproc[(proxy-box [box (and/c box? (not/c immutable?))]
                    [unbox-proc (box? any/c . -> . any/c)]
                    [set-proc (box? any/c . -> . any/c)]
                    [prop proxy-property?]
                    [prop-val any] ... ...)
          (and/c box? proxy?)]{

Returns a proxied value like @scheme[bx], but with
@scheme[unbox] and @scheme[set-box!] operations on the
proxied box redirected.

The @scheme[unbox-proc] must accept @scheme[bx] and the value that
@scheme[unbox] on @scheme[bx] produces index; it must produce a replacement
value, which is the result of
@scheme[unbox] on the proxy.

The @scheme[set-proc] must accept @scheme[bx] and the value passed to
@scheme[set-box!]; it must produce a replacement
value, which is used with @scheme[set-box!] on the original
@scheme[bx] to install the value.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[proxy-box] must be odd) add proxy properties
or override proxy-property values of @scheme[bx].}


@defproc[(proxy-hash [hash (and/c hash? (not/c immutable?))]
                     [ref-proc (hash? any/c . -> . (values 
                                                    any/c 
                                                    (hash? any/c any/c . -> . any/c)))]
                     [set-proc (hash? any/c any/c . -> . (values any/c any/c))]
                     [remove-proc (hash? any/c . -> . any/c)]
                     [key-proc (hash? any/c . -> . any/c)]
                     [prop proxy-property?]
                     [prop-val any] ... ...)
          (and/c hash? proxy?)]{

Returns a proxied value like @scheme[hash], but with
@scheme[hash-ref], @scheme[hash-set!] or @scheme[hash-set] (as
applicable) and @scheme[hash-remove] or @scheme[hash-remove!] (as
application) operations on the proxied hash table redirected. When
@scheme[hash-set] or @scheme[hash-remove] is used on a proxied hash
table, the resulting hash table is given all of the proxys of the
given hash table. In addition, operations like
@scheme[hash-iterate-key] or @scheme[hash-map], which extract
keys from the table, use @scheme[key-proc] to filter keys extracted
from the table. Operations like @scheme[hash-iterate-value] or
@scheme[hash-iterate-map] implicitly use @scheme[hash-ref] and
therefore redirect through @scheme[ref-proc].

The @scheme[ref-proc] must accept @scheme[hash] and a key passed
@scheme[hash-ref]. It must return a replacement key
as well as a procedure. The returned procedure is called only if the
returned key is found in @scheme[hash] via @scheme[hash-ref], in which
case the procedure is called with @scheme[hash], the previously
returned key, and the found value. The returned procedure must itself
return a replecement for the found value.

The @scheme[set-proc] must accept @scheme[hash], a key passed to
@scheme[hash-set!] or @scheme[hash-set], and the value passed to
@scheme[hash-set!] or @scheme[hash-set]; it must produce two values: a
replacement for the key and a replacement for the value. The returned
key and value are used with @scheme[hash-set!] or @scheme[hash-set] on
the original @scheme[hash] to install the value.

The @scheme[remove-proc] must accept @scheme[hash] and a key passed to
@scheme[hash-remove!] or @scheme[hash-remove]; it must produce the a
replacement for the key, which is used with @scheme[hash-remove!] or
@scheme[hash-remove] on the original @scheme[hash] to remove any
mapping using the (proxy-replaced) key.

The @scheme[key-proc] must accept @scheme[hash] and a key that has
been extracted from @scheme[hash] (by @scheme[hash-iterate-key] or
other operations that use @scheme[hash-iterate-key] internally); it
must produce a replacement for the key, which is then reported as a
key extracted from the table.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[proxy-hash] must be odd) add proxy properties
or override proxy-property values of @scheme[hash].}

@; ------------------------------------------------------------
@section{Chaperone Constructors}

@defproc[(chaperone-procedure [proc procedure?]
                              [wrapper-proc procedure?]
                              [prop proxy-property?]
                              [prop-val any] ... ...)
         (and/c procedure? chaperone?)]{

Like @racket[proxy-procedure], but for each value supplied to
@scheme[wrapper-proc], the corresponding result must be the same or a
chaperone of (in the sense of @scheme[chaperone-of?])  the supplied
value. The additional result, if any, that precedes the chaperoned
values must be a procedure that accepts as many results as produced by
@scheme[proc]; it must return the same number of results, each of
which is the same or a chaperone of the corresponding original result.

For applications that include keyword arguments, @scheme[wrapper-proc]
must return an additional value before any other values but after the
result-chaperoning procedure (if any). The additional value must be a
list of chaperones of the keyword arguments that were supplied to the
chaperoned procedure (i.e., not counting optional arguments that were
not supplied). The arguments must be ordered according to the sorted
order of the supplied arguments' keywords.}

@defproc[(chaperone-struct [v any/c]
                           [orig-proc (or/c struct-accessor-procedure?
                                            struct-mutator-procedure?
                                            struct-type-property-accessor-procedure?
                                            (one-of/c struct-info))]
                           [redirect-proc procedure?] ... ...
                           [prop proxy-property?]
                           [prop-val any] ... ...)
          any/c]{

Like @racket[proxy-struct], but with the following refinements:

@itemlist[

 @item{With a structure-field accessor as @racket[orig-proc],
      @scheme[redirect-proc] must accept two arguments, @scheme[v] and
      the value @scheme[_field-v] that @scheme[orig-proc] produces for
      @scheme[v]; it must return chaperone of @scheme[_field-v]. The
      corresponding field may be immutable.}

 @item{A property accessor can be supplied as @racket[orig-proc].  The
       corresponding @racket[redirect-proc] uses the same protocol as
       for a structure-field selector.}

 @item{With structure-field mutator as @racket[orig-proc],
      @scheme[redirect-proc] must accept two arguments, @scheme[v] and
      the value @scheme[_field-v] supplied to the mutator; it must
      return chaperone of @scheme[_field-v] to be propagated to
      @scheme[orig-proc] and @scheme[v].}

 @item{With @scheme[struct-info] as @racket[orig-proc], the
       corresponding @scheme[redirect-proc] must accept two values,
       which are the results of @scheme[struct-info] on @scheme[v]; it
       must return each values or a chaperone of each value. The
       @scheme[redirect-proc] is not called if @scheme[struct-info] would
       return @scheme[#f] as its first argument.}

]

An @scheme[orig-proc] can be @scheme[struct-info] only if some other
@scheme[orig-proc] is supplied, and each @scheme[orig-proc] must
indicate a distinct operation. If no @scheme[orig-proc]s are supplied,
then no @scheme[prop]s must be supplied, and @scheme[v] is returned
unchaperoned.}

@defproc[(chaperone-vector [vec vector?]
                           [ref-proc (vector? exact-nonnegative-integer? any/c . -> . any/c)]
                           [set-proc (vector? exact-nonnegative-integer? any/c . -> . any/c)]
                           [prop proxy-property?]
                           [prop-val any] ... ...)
          (and/c vector? chaperone?)]{

Like @racket[proxy-vector], but with support for mutable vectors. The
@scheme[ref-proc] procedure must produce the same value or a chaperone
of the original value, and @scheme[set-proc] must produce the value
that is given or a chaperone of the value. The @scheme[set-proc] will
not be used if @scheme[vec] is immutable.}

@defproc[(chaperone-box [bx box?]
                        [unbox-proc (box? any/c . -> . any/c)]
                        [set-proc (box? any/c . -> . any/c)]
                        [prop proxy-property?]
                        [prop-val any] ... ...)
          (and/c box? chaperone?)]{

Like @racket[prox-box], but with support for immutable boxes. The
@scheme[unbox-proc] procedure must produce the same value or a
chaperone of the original value, and @scheme[set-proc] must produce
the same value or a chaperone of the value that it is given.  The
@scheme[set-proc] will not be used if @scheme[bx] is immutable.}


@defproc[(chaperone-hash [hash hash?]
                         [ref-proc (hash? any/c . -> . (values 
                                                        any/c 
                                                        (hash? any/c any/c . -> . any/c)))]
                         [set-proc (hash? any/c any/c . -> . (values any/c any/c))]
                         [remove-proc (hash? any/c . -> . any/c)]
                         [key-proc (hash? any/c . -> . any/c)]
                         [prop proxy-property?]
                         [prop-val any] ... ...)
          (and/c hash? chaperone?)]{

Like @racket[proxy-hash], but with constraints on the given functions
and support for immutable hashes. The @scheme[ref-proc] procedure must
return a found value or a chaperone of the value. The
@scheme[set-proc] procedure must produce two values: the key that it
is given or a chaperone of the key and the value that it is given or a
chaperone of the value. The @scheme[remove-proc] and @scheme[key-proc]
procedures must produce the given key or a chaperone of the key.}

@defproc[(chaperone-struct-type [struct-type struct-type?]
                                [struct-info-proc procedure?]
                                [make-constructor-proc (procedure? . -> . procedure?)]
                                [guard-proc procedure?]
                                [prop proxy-property?]
                                [prop-val any] ... ...)
          (and/c struct-type? chaperone?)]{

Returns a chaperoned value like @scheme[struct-type], but with
@scheme[struct-type-info] and @scheme[struct-type-make-constructor]
operations on the chaperoned structure type redirected. In addition,
when a new structure type is created as a subtype of the chaperoned
structure type, @scheme[guard-proc] is interposed as an extra guard on
creation of instances of the subtype.

The @scheme[struct-info-proc] must accept 8 arguments---the result of
@scheme[struct-type-info] on @scheme[struct-type]. It must return 8
values, where each is the same or a chaperone of the corresponding
argument. The 8 values are used as the results of
@scheme[struct-type-info] for the chaperoned structure type.

The @scheme[make-constructor-proc] must accept a single procedure
argument, which is a constructor produced by
@scheme[struct-type-make-constructor] on @scheme[struct-type]. It must
return the same or a chaperone of the procedure, which is used as the
result of @scheme[struct-type-make-constructor] on the chaperoned
structure type.

The @scheme[guard-proc] must accept as many argument as a constructor
for @scheme[struct-type]; it must return the same number of arguments,
each the same or a chaperone of the corresponding argument. The
@scheme[guard-proc] is added as a constructor guard when a subtype is
created of the chaperoned structure type.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[chaperone-struct-type] must be even) add proxy properties
or override proxy-property values of @scheme[struct-type].}

@defproc[(chaperone-evt [evt evt?]
                        [proc (evt? . -> . (values evt? (any/c . -> . any/c)))]
                        [prop proxy-property?]
                        [prop-val any] ... ...)
          (and/c evt? chaperone?)]{

Returns a chaperoned value like @scheme[evt], but with @scheme[proc]
as an event generator when the result is synchronized with functions
like @racket[sync].

The @racket[proc] generator is called on synchronization, much like
the procedure passed to @racket[guard-evt], except that @racket[proc]
is given @scheme[evt]. The @racket[proc] must return two values: a
@tech{synchronizable event} that is a chaperone of @racket[evt], and a
procedure that is used to check the event's result if it is chosen in
a selection. The latter procedure accepts the result of @racket[evt],
and it must return a chaperone of that value.

Pairs of @scheme[prop] and @scheme[prop-val] (the number of arguments
to @scheme[chaperone-struct-type] must be even) add proxy properties
or override proxy-property values of @scheme[evt].}

@; ------------------------------------------------------------
@section{Proxy Properties}

@defproc[(make-proxy-property [name symbol?])
         (values proxy-property?
                 (-> any/c boolean?)
                 (-> chaperone? any))]{

Creates a new @tech{proxy property} and returns three values:

@itemize[

 @item{a @deftech{proxy property descriptor}, for use with
       @scheme[chaperone-procedure], @scheme[chaperone-struct], and
       other chaperone constructors;}

 @item{a @deftech{proxy property predicate} procedure, which takes
       an arbitrary value and returns @scheme[#t] if the value is a
       chaperone with a value for the property, @scheme[#f]
       otherwise;}

 @item{an @deftech{proxy property accessor} procedure, which
       returns the value associated with a chaperone for the property;
       if a value given to the accessor is not a chaperone or does not
       have a value for the property (ie if the corresponding chaperone
       property predicate returns @racket[#f]), the accessor raises
       @exnraise[exn:fail:contract].}

]}

@defproc[(proxy-property? [v any/c]) boolean?]{

Returns @scheme[#t] if @scheme[v] is a @tech{proxy property
descriptor} value, @scheme[#f] otherwise.}

@defproc[(proxy-property-accessor-procedure? [v any/c]) boolean?]{

Returns @scheme[#t] if @scheme[v] is an accessor procedure produced
by @scheme[make-proxy-property], @scheme[#f] otherwise.}
