116 lines
3.7 KiB
Racket
116 lines
3.7 KiB
Racket
#lang scribble/manual
|
|
@(require scribble/struct scribble/decode scribble/eval
|
|
(for-label racket/base racket/contract))
|
|
|
|
@(define the-eval (make-base-eval))
|
|
@(the-eval '(require racket/contract))
|
|
|
|
@title{Structure Type Property Contracts}
|
|
|
|
@defproc[(struct-type-property/c [value-contract contract?])
|
|
contract?]{
|
|
|
|
Produces a contract for a @tech{structure type property}. When the
|
|
contract is applied to a struct type property, it produces a wrapped
|
|
struct type property that applies @racket[value-contract] to the value
|
|
associated with the property when it used to create a new struct type
|
|
(via @racket[struct], @racket[make-struct-type], etc).
|
|
|
|
The struct type property's accessor function is not affected; if it is
|
|
exported, it must be protected separately.
|
|
|
|
As an example, consider the following module. It creates a structure
|
|
type property, @racket[prop], whose value should be a function mapping
|
|
a structure instance to a numeric predicate. The module also exports
|
|
@racket[app-prop], which extracts the predicate from a structure
|
|
instance and applies it to a given value.
|
|
|
|
@interaction[#:eval the-eval
|
|
(module propmod racket
|
|
(require racket/contract)
|
|
(define-values (prop prop? prop-ref)
|
|
(make-struct-type-property 'prop))
|
|
(define (app-prop x v)
|
|
(((prop-ref x) x) v))
|
|
(provide/contract
|
|
[prop? (-> any/c boolean?)]
|
|
[prop (struct-type-property/c
|
|
(-> prop? (-> integer? boolean?)))]
|
|
[app-prop (-> prop? integer? boolean?)])
|
|
(provide prop-ref))
|
|
]
|
|
|
|
The @racket[structmod] module creates a structure type named
|
|
@racket[s] with a single field; the value of @racket[prop] is a
|
|
function that extracts the field value from an instance. Thus the
|
|
field ought to be an integer predicate, but notice that
|
|
@racket[structmod] places no contract on @racket[s] enforcing that
|
|
constraint.
|
|
|
|
@interaction[#:eval the-eval
|
|
(module structmod racket
|
|
(require 'propmod)
|
|
(struct s (f) #:property prop (lambda (s) (s-f s)))
|
|
(provide (struct-out s)))
|
|
(require 'propmod 'structmod)
|
|
]
|
|
|
|
First we create an @racket[s] instance with a integer predicate, so
|
|
the constraint on @racket[prop] is in fact satisfied. The first call
|
|
to @racket[app-prop] is correct; the second simply violates the
|
|
contract of @racket[app-prop].
|
|
|
|
@interaction[#:eval the-eval
|
|
(define s1 (s even?))
|
|
(app-prop s1 5)
|
|
(app-prop s1 'apple)
|
|
]
|
|
|
|
We are able to create @racket[s] instances with values other than
|
|
integer predicates, but applying @racket[app-prop] on them blames
|
|
@racket[structmod], because the function associated with
|
|
@racket[prop]---that is, @racket[(lambda (s) (s-f s))]---does not
|
|
always produce a value satisfying @racket[(-> integer? boolean?)].
|
|
|
|
@interaction[#:eval the-eval
|
|
(define s2 (s "not a fun"))
|
|
(app-prop s2 5)
|
|
|
|
(define s3 (s list))
|
|
(app-prop s3 5)
|
|
]
|
|
|
|
The fix would be to propagate the obligation inherited from
|
|
@racket[prop] to @racket[s]:
|
|
|
|
@racketblock[
|
|
(provide (contract-out
|
|
[struct s ([f (-> integer? boolean?)])]))
|
|
]
|
|
|
|
Finally, if we directly apply the property accessor,
|
|
@racket[prop-ref], and then misuse the resulting function, the
|
|
@racket[propmod] module is blamed:
|
|
|
|
@interaction[#:eval the-eval
|
|
((prop-ref s3) 'apple)
|
|
]
|
|
|
|
The @racket[propmod] module has an obligation to ensure a function
|
|
associated with @racket[prop] is applied only to values satisfying
|
|
@racket[prop?]. By directly providing @racket[prop-ref], it enables
|
|
that constraint to be violated (and thus it is blamed), even though
|
|
the bad application actually occurs elsewhere.
|
|
|
|
Generally there is no need to provide a structure type property
|
|
accessor at all; it is typically only used by other functions within
|
|
the module. But if it must be provided, it should be protected thus:
|
|
|
|
@racketblock[
|
|
(provide (contract-out
|
|
[prop-ref (-> prop? (-> prop? (-> integer? boolean?)))]))
|
|
]
|
|
}
|
|
|
|
@close-eval[the-eval]
|