a little more progress
svn: r8205
This commit is contained in:
parent
1717611b7b
commit
765ec336e0
|
@ -1,201 +1,124 @@
|
||||||
#lang scribble/doc
|
#lang scribble/doc
|
||||||
@require[scribble/manual]
|
@require[scribble/manual]
|
||||||
@require[scribble/eval]
|
@require[scribble/eval]
|
||||||
|
@require[scribble/struct]
|
||||||
@require["guide-utils.ss"]
|
@require["guide-utils.ss"]
|
||||||
@require["contracts-utils.ss"]
|
@require["contracts-utils.ss"]
|
||||||
@(require (for-label scheme/contract))
|
@(require (for-label scheme/contract))
|
||||||
|
|
||||||
<section title="Contracts on Functions in General" tag="genfunc" />
|
@title{Contracts on Functions in General}
|
||||||
|
|
||||||
<question title="Why do the error messages contain "..."?" tag="flat-named-contract">
|
@question[#:tag "flat-named-contracts"]{Why do the error messages contain "..."?}
|
||||||
|
|
||||||
<p>You wrote your module. You added contracts. You put them into the interface
|
You wrote your module. You added contracts. You put them into the interface
|
||||||
so that client programmers have all the information from interfaces. It's a
|
so that client programmers have all the information from interfaces. It's a
|
||||||
piece of art:
|
piece of art:
|
||||||
<scheme>
|
@schememod[
|
||||||
(module myaccount mzscheme
|
scheme/base
|
||||||
(require (lib "contract.ss"))
|
(require scheme/contract)
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[deposit (-> (lambda (x)
|
[deposit (-> (lambda (x)
|
||||||
(and (number? x) (integer? x) (>= x 0)))
|
(and (number? x) (integer? x) (>= x 0)))
|
||||||
any)])
|
any)])
|
||||||
|
|
||||||
(define this 0)
|
(define this 0)
|
||||||
(define (deposit a) ...))
|
(define (deposit a) ...)
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
Several clients used your module. Others used their modules. And all of a sudden
|
Several clients used your module. Others used their
|
||||||
one of them sees this error message:
|
modules. And all of a sudden one of them sees this error
|
||||||
|
message:
|
||||||
|
|
||||||
<blockquote>
|
@(make-element "schemeerror"
|
||||||
<pre><font color="red">
|
'("bank-client broke the contract (-> ??? any)
|
||||||
bank-client broke the contract (-> ??? any)
|
|
||||||
it had with myaccount on deposit;
|
it had with myaccount on deposit;
|
||||||
expected <???>, given: -10
|
expected <???>, given: -10"))
|
||||||
</font></pre>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
Clearly, @scheme[bank-client] is a module that uses @scheme[myaccount]
|
Clearly, @scheme[bank-client] is a module that uses
|
||||||
but what are the '?' doing there? Wouldn't it be nice if we had a name for this
|
@scheme[myaccount] but what are the @tt{?}s doing there?
|
||||||
class of data much like we have string, number, and so on?
|
Wouldn't it be nice if we had a name for this class of data
|
||||||
|
much like we have string, number, and so on?
|
||||||
|
|
||||||
<p>For this situation, PLT Scheme provides "flat named contracts". The use of
|
For this situation, PLT Scheme provides "flat named contracts". The use of
|
||||||
"contract" shows that contracts are first-class values. The "flat" means that
|
"contract" shows that contracts are first-class values. The "flat" means that
|
||||||
the collection of data is a subset of the built-in atomic classes of data; they
|
the collection of data is a subset of the built-in atomic classes of data; they
|
||||||
are described by a predicate that consumes all Scheme values and produces a
|
are described by a predicate that consumes all Scheme values and produces a
|
||||||
boolean. The "named" part says what we want to do: name the contract so that
|
boolean. The "named" part says what we want to do: name the contract so that
|
||||||
error messages become intelligible:
|
error messages become intelligible:
|
||||||
|
|
||||||
<scheme>
|
@schememod[
|
||||||
(module myaccount mzscheme
|
scheme/base
|
||||||
(require (lib "contract.ss"))
|
(require scheme/contract)
|
||||||
|
|
||||||
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
|
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
|
||||||
(define amount (flat-named-contract 'amount amount?))
|
(define amount (flat-named-contract 'amount amount?))
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[deposit (amount . -> . any)])
|
[deposit (amount . -> . any)])
|
||||||
|
|
||||||
(define this 0)
|
(define this 0)
|
||||||
(define (deposit a) ...))
|
(define (deposit a) ...)
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
With this little change, the error message becomes all of sudden quite readable:
|
With this little change, the error message becomes all of
|
||||||
|
sudden quite readable:
|
||||||
|
|
||||||
<blockquote>
|
@(make-element "schemeerror"
|
||||||
<pre><font color="red">
|
'("bank-client broke the contract (-> amount any)
|
||||||
bank-client broke the contract (-> amount any)
|
|
||||||
it had with myaccount on deposit;
|
it had with myaccount on deposit;
|
||||||
expected <amount>, given: -10
|
expected <amount>, given: -10"))
|
||||||
</font></pre>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
</question>
|
@question[#:tag "optionals"]{Can a contract specify what the values of optional arguments to a function must be?}
|
||||||
|
|
||||||
<question title="Can a contract specify that the result depends on the arguments?" tag="arrow-d">
|
Sure, using the @scheme[->*] contract. For example,
|
||||||
|
|
||||||
<p>Here is an excerpt from an imaginary (pardon the pun) <a
|
@question[#:tag "arrow-d"]{Can a contract specify that the result depends on the arguments?}
|
||||||
name="numerics-module">numerics module</a>:
|
|
||||||
|
|
||||||
<scheme>
|
Here is an excerpt from an imaginary (pardon the pun) numerics module:
|
||||||
(module numerics mzscheme
|
|
||||||
(require (lib "contract.ss"))
|
|
||||||
|
|
||||||
(provide/contract
|
@schememod[
|
||||||
[sqrt.v1 (->d (lambda (x) (>= x 1))
|
scheme/base
|
||||||
(lambda (argument)
|
(require scheme/contract)
|
||||||
(lambda (result)
|
|
||||||
(<= result argument))))]
|
(provide/contract
|
||||||
...)
|
[sqrt.v1 (->d ([argument (>=/c 1)])
|
||||||
...)
|
()
|
||||||
</scheme>
|
[result (<=/c argument)])])
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
The contract for the exported function @scheme[sqrt.v1] uses the
|
The contract for the exported function @scheme[sqrt.v1] uses the
|
||||||
@scheme[->d] rather than @scheme[->] function contract. The "d"
|
@scheme[->d] rather than @scheme[->] function contract. The "d"
|
||||||
stands for <em>dependent</em> contract, meaning the contract for the
|
stands for <em>dependent</em> contract, meaning the contract for the
|
||||||
function range depends on the value of the argument.
|
function range depends on the value of the argument.
|
||||||
|
|
||||||
<p>In this particular case, the argument of @scheme[sqrt.v1] is greater
|
In this particular case, the argument of @scheme[sqrt.v1] is greater
|
||||||
or equal to 1. Hence a very basic correctness check is that the result is
|
or equal to 1. Hence a very basic correctness check is that the result is
|
||||||
smaller than the argument. [Naturally, if this function is critical, one
|
smaller than the argument. (Naturally, if this function is critical, one
|
||||||
could strengthen this check with additional clauses.]
|
could strengthen this check with additional clauses.)
|
||||||
|
|
||||||
<p>In general, a dependent function contract checks the argument, then
|
In general, a dependent function contract looks just like
|
||||||
applies the function in the range position to the argument, and uses the
|
the more general @scheme[->*] contract, but with names added
|
||||||
result of this application (usually a closure) as the range check.
|
that can be used elsewhere in the contract.
|
||||||
|
|
||||||
<p>Let's look at a second example to see how this closure creating business
|
Yes, there are many other contract combinators such as @scheme[<=/c]
|
||||||
works so well here. Take a look at the following module, which exports
|
|
||||||
(among other things) a @scheme[deposit] function for a bank
|
|
||||||
account. Whether the programmer implements this bank account imperatively
|
|
||||||
or functionally, the balance of the account should increase when a customer
|
|
||||||
deposits money:
|
|
||||||
|
|
||||||
<a name="deposit" />
|
|
||||||
<scheme>
|
|
||||||
(module account mzscheme
|
|
||||||
(require (lib "contract.ss"))
|
|
||||||
|
|
||||||
(define-struct account (balance))
|
|
||||||
(define amount natural-number/c)
|
|
||||||
|
|
||||||
(provide/contract
|
|
||||||
;; create an account with the given initial deposit
|
|
||||||
[create (amount . -> . account?)]
|
|
||||||
|
|
||||||
;; what is the current balance of the account?
|
|
||||||
[balance (account . -> . amount)]
|
|
||||||
|
|
||||||
;; deposit the given amount into the given account
|
|
||||||
;; account? amount -> account?
|
|
||||||
[deposit (->d account?
|
|
||||||
amount
|
|
||||||
(lambda (account amount)
|
|
||||||
(let ([balance0 (balance account)])
|
|
||||||
(lambda (result)
|
|
||||||
(and (account? result)
|
|
||||||
(<= balance0 (balance result)))))))]
|
|
||||||
... )
|
|
||||||
|
|
||||||
...
|
|
||||||
)
|
|
||||||
</scheme>
|
|
||||||
|
|
||||||
<p>The module implements the creation of accounts, balance checks, account
|
|
||||||
deposits, and many other functions presumably. In principle, the
|
|
||||||
@scheme[deposit] function consumes an account and an amount, adds the
|
|
||||||
amount to the account, and returns the (possibly modified) account.
|
|
||||||
|
|
||||||
<p>To ensure the increase in the account's balance, the contract is a
|
|
||||||
dependent contract. The function on the right of the @scheme[->d]
|
|
||||||
contract combinator consumes the two argument values: an account and an
|
|
||||||
amount. It then returns a function that checks @scheme[deposit]'s
|
|
||||||
result. In this case, it checks whether the result is an account and
|
|
||||||
whether @scheme[balance0] is smaller than the account's balance. Note
|
|
||||||
that @scheme[balance0] is the value that @scheme[balance] extracted
|
|
||||||
from the given account <em>before</em> the range-checking function was
|
|
||||||
produced.
|
|
||||||
|
|
||||||
</question>
|
|
||||||
|
|
||||||
<question title="See: currying would help with contracts!" tag="curry">
|
|
||||||
|
|
||||||
<p>Exactly!
|
|
||||||
|
|
||||||
<p>The contract for @scheme[sqrt.v1] suggests that curried versions of
|
|
||||||
the numeric comparison operators would come in handy for defining contracts
|
|
||||||
and combining them with contract combinators such as @scheme[->d].
|
|
||||||
|
|
||||||
<p>Here is a revision of <a href="#numerics-module">the
|
|
||||||
@scheme[numerics] module</a> that exploits these special contract
|
|
||||||
combinators:
|
|
||||||
|
|
||||||
<scheme>
|
|
||||||
(module numerics mzscheme
|
|
||||||
(require (lib "contract.ss"))
|
|
||||||
|
|
||||||
(provide/contract
|
|
||||||
[sqrt.2 (->d (>=/c 1.0) <=/c)]
|
|
||||||
...)
|
|
||||||
...)
|
|
||||||
</scheme>
|
|
||||||
|
|
||||||
The combinator @scheme[>=/c] consumes a number @scheme[x] and
|
|
||||||
produces a contract. This contract also consumes a second number, say
|
|
||||||
@scheme[y], and makes sure that @scheme[(>= y x)] holds. The
|
|
||||||
contract combinator @scheme[<=/c] works in an analogous fashion.
|
|
||||||
|
|
||||||
<p>Thus in the above example, the contract for @scheme[sqrt.v2] makes
|
|
||||||
sure that its argument is greater or equal to 1.0 and that its result is
|
|
||||||
less than or equal to its argument.
|
|
||||||
|
|
||||||
<p>Yes, there are many other contract combinators such as @scheme[<=/c]
|
|
||||||
and @scheme[>=/c], and it pays off to look them up in the contract
|
and @scheme[>=/c], and it pays off to look them up in the contract
|
||||||
report section (of the mzlib manual). They simplify contracts tremendously
|
section of the reference manual. They simplify contracts tremendously
|
||||||
and make them more accessible to potential clients.
|
and make them more accessible to potential clients.
|
||||||
|
|
||||||
</question>
|
@;{
|
||||||
|
|
||||||
|
To add: keywords, optional arguments.
|
||||||
|
|
||||||
|
in dependent contracts, discuss what happens when a
|
||||||
|
dependent contract with optional arguments doesn't appear at
|
||||||
|
the call site.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@;{
|
||||||
|
|
||||||
<question title="Can a contract specify that arguments depend on each other?" tag="arrow-r">
|
<question title="Can a contract specify that arguments depend on each other?" tag="arrow-r">
|
||||||
|
|
||||||
|
@ -665,3 +588,4 @@ To specify such contracts combine @scheme[case->] with
|
||||||
the other function contract combinators, like we did in
|
the other function contract combinators, like we did in
|
||||||
the @scheme[substring1] function above.
|
the @scheme[substring1] function above.
|
||||||
|
|
||||||
|
}
|
|
@ -178,7 +178,7 @@ scheme/base
|
||||||
|
|
||||||
Lesson: look up the built-in contracts.
|
Lesson: look up the built-in contracts.
|
||||||
|
|
||||||
@question[#:tag "and-or"]{Are and/c and or/c contract combinators? What of listof?}
|
@question[#:tag "and-or"]{Are @scheme[and/c] and @scheme[or/c] contract combinators? What of @scheme[listof]?}
|
||||||
|
|
||||||
The short answer is yes. Both @scheme[and/c] and @scheme[or/c]
|
The short answer is yes. Both @scheme[and/c] and @scheme[or/c]
|
||||||
ombine contracts and they do what you expect them to do.
|
ombine contracts and they do what you expect them to do.
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
@include-section["contracts-intro.scrbl"]
|
@include-section["contracts-intro.scrbl"]
|
||||||
@include-section["contracts-simple-function.scrbl"]
|
@include-section["contracts-simple-function.scrbl"]
|
||||||
@;{
|
|
||||||
@include-section["contracts-general-function.scrbl"]
|
@include-section["contracts-general-function.scrbl"]
|
||||||
|
@;{
|
||||||
@include-section["contracts-structure.scrbl"]
|
@include-section["contracts-structure.scrbl"]
|
||||||
@include-section["contracts-class.scrbl"]
|
@include-section["contracts-class.scrbl"]
|
||||||
@include-section["contracts-example.scrbl"]
|
@include-section["contracts-example.scrbl"]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user