ported all of the contracts guide except the examples section
svn: r8242
This commit is contained in:
parent
4ba06b7ae0
commit
1644b86aa8
|
@ -3,7 +3,9 @@
|
|||
@require[scribble/eval]
|
||||
@require["guide-utils.ss"]
|
||||
@require["contracts-utils.ss"]
|
||||
@(require (for-label scheme/contract))
|
||||
@(require (for-label scheme/contract)
|
||||
(for-label srfi/13/string)
|
||||
(for-label scheme/gui))
|
||||
|
||||
@title{Contracts on Functions in General}
|
||||
|
||||
|
@ -13,8 +15,7 @@ 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
|
||||
piece of art:
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
[deposit (-> (lambda (x)
|
||||
|
@ -46,8 +47,7 @@ boolean. The "named" part says what we want to do: name the contract so that
|
|||
error messages become intelligible:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
|
||||
(define amount (flat-named-contract 'amount amount?))
|
||||
|
@ -66,18 +66,220 @@ sudden quite readable:
|
|||
it had with myaccount on deposit;
|
||||
expected <amount>, given: -10"}
|
||||
|
||||
@question[#:tag "optionals"]{Can a contract specify what the values of optional arguments to a function must be?}
|
||||
@question[#:tag "optional"]{What about optional arguments?}
|
||||
|
||||
Sure, using the @scheme[->*] contract. For example,
|
||||
Take a look at this excerpt from a string-processing module, inspired by the
|
||||
Scheme cookbook:
|
||||
@schememod[
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
;; pad the given str left and right with
|
||||
;; the (optional) char so that it is centered
|
||||
[string-pad-center (->* (string? natural-number/c)
|
||||
(char?)
|
||||
string?)])
|
||||
|
||||
(define (string-pad-center str width [pad #\space])
|
||||
(define field-width (min width (string-length str)))
|
||||
(define rmargin (ceiling (/ (- width field-width) 2)))
|
||||
(define lmargin (floor (/ (- width field-width) 2)))
|
||||
(string-append (build-string lmargin (λ (x) pad))
|
||||
str
|
||||
(build-string rmargin (λ (x) pad))))
|
||||
]
|
||||
|
||||
The module exports @scheme[string-pad-center], a function that creates a
|
||||
string of a given @scheme[width] with the given string in the center. The
|
||||
default fill character is @scheme[#\space]; if the client module requires
|
||||
different character, it may call @scheme[string-pad-center] with a third
|
||||
argument, a @scheme[char], overwriting the default.
|
||||
|
||||
The function definition uses optional arguments of
|
||||
@scheme[lambda], which is appropriate for this kind of
|
||||
functionality. The interesting point here is the formulation
|
||||
of the contract for the @scheme[string-pad-center].
|
||||
|
||||
|
||||
Like the contract combinator for
|
||||
@scheme[->*], i.e., rest arguments, @scheme[opt->] demands several
|
||||
groups of contracts:
|
||||
@itemize{
|
||||
@item{The first one is a parenthesized group of contracts for all required
|
||||
arguments. In this example, we see two: @scheme[string?] and
|
||||
@scheme[natural-number/c]. }
|
||||
|
||||
@item{The second one is a parenthesized group of contracts for all optional
|
||||
arguments: @scheme[char?]. }
|
||||
|
||||
@item{The last one is a single contract: the result of the function.}
|
||||
}
|
||||
Note if a default value does not satisfy a contract, you won't get a contract
|
||||
error for this interface. We do trust you; if you can't trust
|
||||
yourself, you need to communicate across boundaries for everything you write.
|
||||
|
||||
The contract library does not have built-in combinators to
|
||||
specify richer contracts for functions that have optional
|
||||
arguments, like functions that have optional arguments where
|
||||
the arguments depend on each other.
|
||||
|
||||
To specify such contracts combine @scheme[case->] with
|
||||
the other function contract combinators, like we did in
|
||||
the @scheme[substring1] function above.
|
||||
}
|
||||
|
||||
@question[#:tag "rest-args"]{What about rest arguments?}
|
||||
|
||||
We all know that @scheme[+] in Beginner Scheme is a function
|
||||
that consumes at least two numbers but, in principle,
|
||||
arbitraily many more. Defining the function is easy:
|
||||
@schemeblock[
|
||||
(define (plus fst snd . rst)
|
||||
(foldr + (+ fst snd) rst))
|
||||
]
|
||||
Describing this function via a contract is difficult because of the rest
|
||||
argument (@scheme[rst]).
|
||||
|
||||
Here is the contract:
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[plus (->* (number? number?) () #:rest (listof number?) number?)])
|
||||
]
|
||||
The @scheme[->*] contract combinator empowers you to specify
|
||||
functions that consume a variable number of arguments or functions like
|
||||
@scheme[plus], which consume "at least this number" of arguments but
|
||||
an arbitrary number of additional arguments.
|
||||
|
||||
The contracts for the required arguments are enclosed in the first
|
||||
pair of parentheses:
|
||||
@schemeblock[
|
||||
(number? number?)
|
||||
]
|
||||
|
||||
For @scheme[plus] they demand two numbers. The empty pair of
|
||||
parenthesis indicates that there are no optional arguments
|
||||
(not counting the rest arguments) and the contract for the
|
||||
rest argument follows @scheme[#:rest]
|
||||
@schemeblock[
|
||||
(listof number?)
|
||||
]
|
||||
Since the remainder of the actual arguments are collected
|
||||
in a list for a rest parameter such as @scheme[rst], the
|
||||
contract demands a list of values; in this specific
|
||||
examples, these values must be number.
|
||||
|
||||
Finally, you may have noticed that the contract for the function range
|
||||
of @scheme[plus] is also wrapped in a pair of parentheses. The reason
|
||||
for those is that functions can return multiple values not just one, and
|
||||
this is our next topic in this guide.
|
||||
|
||||
@question[#:tag "keywords"]{What about keyword arguments?}
|
||||
|
||||
Sometimes, a function accepts many arguments and remembering
|
||||
their order can be a nightmare. To help with such functions,
|
||||
PLT Scheme has @seclink["lambda-keywords"]{keyword} arguments.
|
||||
|
||||
For example, consider this function that creates a simple
|
||||
GUI and asks the user a yes-or-no question:
|
||||
@schememod[
|
||||
scheme/gui
|
||||
|
||||
(define (ask-yes-or-no-question #:question question
|
||||
#:default def
|
||||
#:title title
|
||||
#:width w
|
||||
#:height h)
|
||||
(define d (new dialog% [label title] [width w] [height h]))
|
||||
(define answer def)
|
||||
(define msg (new message% [label question] [parent d]))
|
||||
(define (yes) (set! answer #t) (send d show #f))
|
||||
(define (no) (set! answer #f) (send d show #f))
|
||||
(define yes-b (new button%
|
||||
[label "Yes"]
|
||||
[parent d]
|
||||
[callback (λ (x y) (yes))]
|
||||
[style (if def '(border) '())]))
|
||||
(define no-b (new button%
|
||||
[label "No"]
|
||||
[parent d]
|
||||
[callback (λ (x y) (no))]
|
||||
[style (if def '() '(border))]))
|
||||
(send d show #t)
|
||||
answer)
|
||||
|
||||
(provide/contract
|
||||
[ask-yes-or-no-question
|
||||
(-> #:question string?
|
||||
#:default boolean?
|
||||
#:title string?
|
||||
#:width exact-integer?
|
||||
#:height exact-integer?
|
||||
boolean?)])
|
||||
]
|
||||
@margin-note{Note that if you really want to ask a yes-or-no
|
||||
question via a GUI, you should use
|
||||
@scheme[message-box/custom] (and generally speaking,
|
||||
avoiding the words ``yes'' and ``no'' in your dialog is a
|
||||
good idea, too ...)}
|
||||
|
||||
The contract for @scheme[ask-yes-or-no-question] uses our
|
||||
old friend the @scheme[->] contract combinator. Just like
|
||||
@scheme[lambda] (or @scheme[define]-based functions) use
|
||||
keywords for specifying keyword arguments, it uses keywords
|
||||
for specifying contracts on keyword arguments. In this case,
|
||||
it says that @scheme[ask-yes-or-no-question] must receive
|
||||
five keyword arguments, one for each of the keywords
|
||||
@scheme[#:question],
|
||||
@scheme[#:default],
|
||||
@scheme[#:title],
|
||||
@scheme[#:width], and
|
||||
@scheme[#:height].
|
||||
Also, just like in a function definition, the keywords in
|
||||
the @scheme[->] may appear in any order.
|
||||
|
||||
@question[#:tag "optional-keywords"]{What about optional keyword arguments?}
|
||||
|
||||
Of course, many of the parameters in
|
||||
@scheme[ask-yes-or-no-question] (from the previous question)
|
||||
have reasonable defaults, and should be made optional:
|
||||
@schemeblock[
|
||||
(define (ask-yes-or-no-question #:question question
|
||||
#:default def
|
||||
#:title [title "Yes or No?"]
|
||||
#:width [w 400]
|
||||
#:height [h 200])
|
||||
...)
|
||||
]
|
||||
|
||||
To specify this function's contract, we need to use
|
||||
@scheme[->*]. It too supports keywords just as you might
|
||||
expect, in both the optional and mandatory argument
|
||||
sections. In this case, we have mandatory keywords
|
||||
@scheme[#:question] and @scheme[#:default], and optional keywords
|
||||
@scheme[#:title],
|
||||
@scheme[#:width], and
|
||||
@scheme[#:height]. So, we write the contract like this:
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[ask-yes-or-no-question
|
||||
(->* (#:question string?
|
||||
#:default boolean?)
|
||||
|
||||
(#:title string?
|
||||
#:width exact-integer?
|
||||
#:height exact-integer?)
|
||||
|
||||
boolean?)])
|
||||
]
|
||||
putting the mandatory keywords in the first section and the
|
||||
optional ones in the second section.
|
||||
|
||||
@question[#:tag "arrow-d"]{Can a contract specify that the result depends on the arguments?}
|
||||
|
||||
Here is an excerpt from an imaginary (pardon the pun) numerics module:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
|
||||
scheme
|
||||
(provide/contract
|
||||
[sqrt.v1 (->d ([argument (>=/c 1)])
|
||||
()
|
||||
|
@ -87,7 +289,7 @@ scheme/base
|
|||
|
||||
The contract for the exported function @scheme[sqrt.v1] uses the
|
||||
@scheme[->d] rather than @scheme[->] function contract. The "d"
|
||||
stands for <em>dependent</em> contract, meaning the contract for the
|
||||
stands for @italic{dependent} contract, meaning the contract for the
|
||||
function range depends on the value of the argument.
|
||||
|
||||
In this particular case, the argument of @scheme[sqrt.v1] is greater
|
||||
|
@ -104,244 +306,153 @@ and @scheme[>=/c], and it pays off to look them up in the contract
|
|||
section of the reference manual. They simplify contracts tremendously
|
||||
and make them more accessible to potential clients.
|
||||
|
||||
@;{
|
||||
@question[#:tag "arrow-d-args"]{Can a contract specify that arguments depend on each other?}
|
||||
|
||||
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">
|
||||
|
||||
<p>Eventually bank customers want their money back. Hence, a module that
|
||||
Eventually bank customers want their money back. Hence, a module that
|
||||
implements a bank account must include a method for withdrawing money. Of
|
||||
course, ordinary accounts don't let customers withdraw an arbitrary amount of
|
||||
money but only as much as they have in the account.
|
||||
|
||||
<p>Suppose the account module provides the following two functions:
|
||||
<scheme>
|
||||
;; balance : Account -> Amount
|
||||
;; withdraw : Account Amount -> Account
|
||||
</scheme>
|
||||
Suppose the account module provides the following two functions:
|
||||
@schemeblock[
|
||||
(code:comment "balance : account -> amount")
|
||||
(code:comment "withdraw : account amount -> account")
|
||||
]
|
||||
Then, informally, the proper precondition for @scheme[withdraw] is that
|
||||
<blockquote>
|
||||
"the balance of the given account is greater than or equal to the given (withdrawal) amount."
|
||||
</blockquote>
|
||||
The postcondition is similar to the one for <a
|
||||
href="#deposit">@scheme[deposit]</a>:
|
||||
<blockquote>
|
||||
"the balance of the resulting account is larger than (or equal to) than the one of the
|
||||
given account."
|
||||
</blockquote>
|
||||
``the balance of the given account is greater than or equal to the given (withdrawal) amount.''
|
||||
The postcondition is similar to the one for
|
||||
@questionlink["flat-named-contracts"]{@scheme[deposit]}:
|
||||
``the balance of the resulting account is larger than (or equal to) than the one of the
|
||||
given account.''
|
||||
You could of course also formulate a full-fledged correctness condition, namely,
|
||||
that the balance of the resulting account is equal to the balance of the given
|
||||
one, plus the given amount.
|
||||
|
||||
<p>The following module implements accounts imperatively and specifies the
|
||||
The following module implements accounts imperatively and specifies the
|
||||
conditions we just discussed:
|
||||
<table>
|
||||
<tr><td>
|
||||
<scheme>
|
||||
(module account mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
<font color="deeppurple">
|
||||
(define-struct account (balance))
|
||||
(define amount natural-number/c)
|
||||
@schememod[
|
||||
scheme
|
||||
|
||||
(code:comment "the contract definitions")
|
||||
(define-struct account (balance))
|
||||
(define amount natural-number/c)
|
||||
|
||||
(define msg>
|
||||
"account a with balance larger than ~a expected")
|
||||
(define msg<
|
||||
"account a with balance less than ~a expected")
|
||||
(define msg> "account a with balance larger than ~a expected")
|
||||
(define msg< "account a with balance less than ~a expected")
|
||||
|
||||
(define (mk-account-contract acc amt op msg)
|
||||
(define balance0 (balance acc))
|
||||
(define (ctr a)
|
||||
(and (account? a) (op balance0 (balance a))))
|
||||
(flat-named-contract (format msg balance0) ctr))</font>
|
||||
<font color="purple">
|
||||
(provide/contract
|
||||
[create (amount . -> . account?)]
|
||||
[balance (account? . -> . amount)]
|
||||
[withdraw (->r ([acc account?]
|
||||
[amt (and/c amount (</c (balance acc)))])
|
||||
(mk-account-contract acc amt > msg>))]
|
||||
[deposit (->r ([acc account?]
|
||||
[amt amount])
|
||||
(mk-account-contract acc amt < msg<))])
|
||||
</font>
|
||||
(define balance account-balance)
|
||||
(define (mk-account-contract acc amt op msg)
|
||||
(define balance0 (balance acc))
|
||||
(define (ctr a)
|
||||
(and (account? a) (op balance0 (balance a))))
|
||||
(flat-named-contract (format msg balance0) ctr))
|
||||
|
||||
(code:comment "the exports")
|
||||
(provide/contract
|
||||
[create (amount . -> . account?)]
|
||||
[balance (account? . -> . amount)]
|
||||
[withdraw (->d ([acc account?]
|
||||
[amt (and/c amount (<=/c (balance acc)))])
|
||||
()
|
||||
[result (mk-account-contract acc amt > msg>)])]
|
||||
[deposit (->d ([acc account?]
|
||||
[amt amount])
|
||||
()
|
||||
[result (mk-account-contract acc amt < msg<)])])
|
||||
|
||||
(code:comment "the function definitions")
|
||||
(define balance account-balance)
|
||||
|
||||
(define (create amt) (make-account amt))
|
||||
(define (create amt) (make-account amt))
|
||||
|
||||
(define (withdraw acc amt)
|
||||
(set-account-balance! acc (- (balance acc) amt))
|
||||
acc)
|
||||
(define (withdraw acc amt)
|
||||
(set-account-balance! acc (- (balance acc) amt))
|
||||
acc)
|
||||
|
||||
(define (deposit acc amt)
|
||||
(set-account-balance! acc (+ (balance acc) amt))
|
||||
acc))
|
||||
</scheme>
|
||||
<td bgcolor="beige" valign="top">
|
||||
<pre>
|
||||
(define (deposit acc amt)
|
||||
(set-account-balance! acc (+ (balance acc) amt))
|
||||
acc)
|
||||
]
|
||||
|
||||
The second section is the export interface: @itemize{
|
||||
@item{@scheme[create] consumes an initial deposit and
|
||||
produces an account. This kind of contract is just like a
|
||||
type in a statically typed language, except that statically
|
||||
typed languages usually don't support the type ``natural
|
||||
numbers'' (as a full-fledged subtype of numbers). }
|
||||
|
||||
the contract definitions
|
||||
@item{@scheme[balance] consumes an account and computes its current balance.}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
the exports
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
the function definitions
|
||||
|
||||
</pre>
|
||||
</table>
|
||||
|
||||
The purple part is the export interface:
|
||||
<ol>
|
||||
<li>@scheme[create] consumes an initial deposit and produces an
|
||||
account. This kind of contract is just like a type in a statically typed
|
||||
language, except that statically typed languages usually don't support the type
|
||||
"natural numbers" (as a full-fledged subtype of numbers).
|
||||
</li>
|
||||
|
||||
<li>@scheme[balance] consumes an account and computes its current balance.
|
||||
</li>
|
||||
|
||||
<li>@scheme[withdraw] consumes an account, named @scheme[acc], and an
|
||||
@item{@scheme[withdraw] consumes an account, named @scheme[acc], and an
|
||||
amount, @scheme[amt]. In addition to being an @scheme[amount], the
|
||||
latter must also be less than @scheme[(balance acc)], i.e., the balance of
|
||||
the given account. That is, the contract for @scheme[amt] depends on the
|
||||
value of @scheme[acc], which is what the @scheme[->r] (r for recursive)
|
||||
value of @scheme[acc], which is what the @scheme[->d]
|
||||
contract combinator expresses.
|
||||
|
||||
<p>The result contract is formed on the fly: @scheme[(mk-account-contract acc amt
|
||||
> msg>)]. It is an application of a contract-producing function that
|
||||
The result contract is formed on the fly:
|
||||
@scheme[(mk-account-contract acc amt > msg>)].
|
||||
It is an application of a contract-producing function that
|
||||
consumes an account, an amount, a comparison operator, and an error message (a
|
||||
format string). The result is a contract.
|
||||
</li>
|
||||
}
|
||||
|
||||
<li>@scheme[deposit]'s contract has been reformulated using the
|
||||
@scheme[->r] combinator. Strictly speaking, this isn't necessary and the use
|
||||
of @scheme[->d] would suffice, because the contracts for the arguments do
|
||||
not depend on each other.
|
||||
</li>
|
||||
</ol>
|
||||
@item{@scheme[deposit]'s contract has been reformulated using the
|
||||
@scheme[->d] combinator. }
|
||||
}
|
||||
|
||||
The code in deep purple defines all those pieces that are needed for the
|
||||
formulation of the export contracts: @scheme[account?], @scheme[amount],
|
||||
error messages (format strings), and @scheme[mk-account-contract]. The
|
||||
latter is a function that extracts the current balance from the given account
|
||||
and then returns a named contract, whose error message (contract name) is a
|
||||
string that refers to this balance. The resulting contract checks whether an
|
||||
account has a balance that is larger or smaller, depending on the given
|
||||
comparison operator, than the original balance.
|
||||
The code in the first section defines all those pieces that
|
||||
are needed for the formulation of the export contracts:
|
||||
@scheme[account?], @scheme[amount], error messages (format
|
||||
strings), and @scheme[mk-account-contract]. The latter is a
|
||||
function that extracts the current balance from the given
|
||||
account and then returns a named contract, whose error
|
||||
message (contract name) is a string that refers to this
|
||||
balance. The resulting contract checks whether an account
|
||||
has a balance that is larger or smaller, depending on the
|
||||
given comparison operator, than the original balance.
|
||||
|
||||
</question>
|
||||
@question[#:tag "case-lambda"]{What about case-lambda?}
|
||||
|
||||
<question title="What about rest arguments?" tag="rest-args">
|
||||
Dybvig, in Chapter 5 of the
|
||||
@link["http://www.scheme.com/csug/"]{Chez Scheme User's Guide},
|
||||
explains the meaning and pragmatics of
|
||||
@scheme[case-lambda] with the following example (among
|
||||
others):
|
||||
|
||||
<p>We all know that @scheme[+] in Beginner Scheme is a function that
|
||||
consumes at least two numbers but, in principle, arbitrary
|
||||
manner. Defining the function is easy:
|
||||
<scheme>
|
||||
(define (plus fst snd . rst)
|
||||
(foldr + (+ fst snd) rst))
|
||||
</scheme>
|
||||
Describing this function via a contract is difficult because of the rest
|
||||
argument (@scheme[rst]).
|
||||
|
||||
<p>Here is the contract:
|
||||
<scheme>
|
||||
(provide/contract
|
||||
[plus (->* (number? number?) (listof number?) (number?))])
|
||||
</scheme>
|
||||
The @scheme[->*] contract combinator empowers you to specify
|
||||
functions that consume a variable number of arguments or functions like
|
||||
@scheme[plus], which consume "at least this number" of arguments but
|
||||
an arbitrary number of additional arguments.
|
||||
|
||||
<p>The contracts for the required arguments are enclosed in an additional
|
||||
pair of parentheses:
|
||||
<scheme>
|
||||
(number? number?)
|
||||
</scheme>
|
||||
For @scheme[plus] they demand two numbers. The contract for the
|
||||
rest argument follows:
|
||||
<scheme>
|
||||
(listof number?)
|
||||
</scheme>
|
||||
Since the remainder of the actual arguments are collected in a list for
|
||||
a @scheme[rest] parameter such as @scheme[rst], the contract
|
||||
demands a list of values; in this specific examples, these values must be
|
||||
number.
|
||||
|
||||
<p>Finally, you may have noticed that the contract for the function range
|
||||
of @scheme[plus] is also wrapped in a pair of parentheses. The reason
|
||||
for those is that functions can return multiple values not just one, and
|
||||
this is our next topic in this guide.
|
||||
</question>
|
||||
|
||||
<question title="What about case-lambda?" tag="case-lambda">
|
||||
|
||||
<p><a href="http://www.scheme.com/csug/binding.html">Dybvig</a> explains
|
||||
the meaning and pragmatics of @scheme[case-lambda] with the following
|
||||
example (among others):
|
||||
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(define substring1
|
||||
(case-lambda
|
||||
[(s) (substring1 s 0 (string-length s))]
|
||||
[(s start) (substring1 s start (string-length s))]
|
||||
[(s start end) (substring s start end)]))
|
||||
</scheme>
|
||||
]
|
||||
This version of @scheme[substring] has one of the following signature:
|
||||
<ol>
|
||||
<li>just a string, in which case it copies the string;
|
||||
<li>a string and an index into the string, in which case it extracts the
|
||||
suffix of the string starting at the index; or
|
||||
<li>a string a start index and an end index, in which case it extracts the
|
||||
fragment of the string between the two indices.
|
||||
</ol>
|
||||
@itemize{
|
||||
@item{just a string, in which case it copies the string;}
|
||||
@item{a string and an index into the string, in which case it extracts the
|
||||
suffix of the string starting at the index; or }
|
||||
@item{a string a start index and an end index, in which case it extracts the
|
||||
fragment of the string between the two indices. }
|
||||
}
|
||||
|
||||
<p>The contract for such a function is formed with the @scheme[case->]
|
||||
The contract for such a function is formed with the @scheme[case->]
|
||||
combinator, which combines as many functional contracts as needed:
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[substring1 (case->
|
||||
(string? . -> . string?)
|
||||
(string? natural-number/c . -> . string?)
|
||||
(string? natural-number/c natural-number/c . -> . string?))])
|
||||
</scheme>
|
||||
[substring1
|
||||
(case->
|
||||
(string? . -> . string?)
|
||||
(string? natural-number/c . -> . string?)
|
||||
(string? natural-number/c natural-number/c . -> . string?))])
|
||||
]
|
||||
As you can see, the contract for @scheme[substring1] combines three
|
||||
function contracts, just as many clauses as the explanation of its
|
||||
functionality required.
|
||||
|
||||
<p>In the case of @scheme[substring1], we also know that the indices
|
||||
@;{
|
||||
This isn't supported anymore (yet...?). -robby
|
||||
|
||||
In the case of @scheme[substring1], we also know that the indices
|
||||
that it consumes ought to be natural numbers less than the length of the
|
||||
given string. Since @scheme[case->] just combines arrow contracts,
|
||||
adding such constraints is just a matter of strengthening the individual
|
||||
|
@ -362,66 +473,70 @@ comparison operator, than the original balance.
|
|||
</scheme>
|
||||
Here we used @scheme[->r] to name the parameters and express the
|
||||
numeric constraints on them.
|
||||
</question>
|
||||
}
|
||||
|
||||
@question[#:tag "multiple"]{What about multiple values?}
|
||||
|
||||
<question title="What about multiple values?" tag="multiple">
|
||||
|
||||
<p>The function @scheme[split] consumes a list of @scheme[char]s
|
||||
The function @scheme[split] consumes a list of @scheme[char]s
|
||||
and delivers the string that occurs before the first occurrence of
|
||||
@scheme[#\newline] (if any) and the rest of the list:
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(define (split l)
|
||||
(define (split l w)
|
||||
(cond
|
||||
[(null? l) (values (list->string (reverse w)) '())]
|
||||
[(char=? #\newline (car l)) (values (list->string (reverse w)) (cdr l))]
|
||||
[(char=? #\newline (car l))
|
||||
(values (list->string (reverse w)) (cdr l))]
|
||||
[else (split (cdr l) (cons (car l) w))]))
|
||||
(split l '()))
|
||||
</scheme>
|
||||
]
|
||||
It is a typical multiple-value function, returning two values by
|
||||
traversing a single list.
|
||||
|
||||
<p>
|
||||
The contract for such a function can use the ordinary
|
||||
function arrow @scheme[->], since it
|
||||
treats @scheme[values] specially, when it appears as the
|
||||
last result:
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[split (-> (listof char?)
|
||||
(values string? (listof char?)))])
|
||||
</scheme>
|
||||
</p>
|
||||
]
|
||||
|
||||
<p>The contract for such a function can also be written
|
||||
using @scheme[->*], just like
|
||||
@scheme[plus]:
|
||||
<scheme>
|
||||
The contract for such a function can also be written
|
||||
using @scheme[->*], just like @scheme[plus]:
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[split (->* ((listof char?))
|
||||
(string? (listof char?)))])
|
||||
</scheme>
|
||||
As before the contract for the single argument is wrapped
|
||||
in an extra pair of parentheses (and must always be wrapped
|
||||
like that); so are the contracts for the results: a string
|
||||
and a list of characters.
|
||||
</p>
|
||||
()
|
||||
(values string? (listof char?)))])
|
||||
]
|
||||
As before the contract for the argument is wrapped in an
|
||||
extra pair of parentheses (and must always be wrapped like
|
||||
that) and the empty pair of parentheses indicates that
|
||||
there are no optoinal arguments. The contracts for the
|
||||
results are inside @scheme[values]: a string and a list of
|
||||
characters.
|
||||
|
||||
<p>Now suppose we also want to ensure that the first result of
|
||||
Now suppose we also want to ensure that the first result of
|
||||
@scheme[split] is a prefix of the given word in list format. In that
|
||||
case, we need to combine the @scheme[->*] contract combinator with the
|
||||
@scheme[->d] combinator, which is of course the @scheme[->d*]
|
||||
combinator:
|
||||
<scheme>
|
||||
case, we need to use the @scheme[->d] contract combinator:
|
||||
@schemeblock[
|
||||
(define (substring-of? s)
|
||||
(flat-named-contract
|
||||
(format "substring of ~s" s)
|
||||
(lambda (s2)
|
||||
(and (string? s2)
|
||||
(<= (string-length s2) s)
|
||||
(equal? (substring s 0 (string-length s2)) s2)))))
|
||||
|
||||
(provide/contract
|
||||
[splitp (->d* ((listof char?))
|
||||
(lambda (fl)
|
||||
(define wd (list->string fl))
|
||||
(values (and/c string? (lambda (w) (string<=? w wd)))
|
||||
(listof char?))))])
|
||||
</scheme>
|
||||
Like @scheme[->d], the new combinator uses a function over the
|
||||
[split (->d ([fl (listof char?)])
|
||||
()
|
||||
(values [s (substring-of (list->string fl))]
|
||||
[c (listof char?)]))])
|
||||
]
|
||||
Like @scheme[->*], the new combinator uses a function over the
|
||||
argument to create the range contracts. Yes, it doesn't just return one
|
||||
contract but as many as the function produces values: one contract per
|
||||
value. In this case, the second contract is the same as before, ensuring
|
||||
|
@ -429,37 +544,32 @@ using @scheme[->*], just like
|
|||
first contract strengthens the old one so that the result is a prefix of
|
||||
the given word.
|
||||
|
||||
<p>This contract is expensive to check of course. Here is a slightly
|
||||
This contract is expensive to check of course. Here is a slightly
|
||||
cheaper version:
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[splitl (->d* ((listof char?))
|
||||
(lambda (fl)
|
||||
(values (and/c string? (string/len (add1 (length fl))))
|
||||
(listof char?))))])
|
||||
</scheme>
|
||||
Check the help desk for an explanation of @scheme[string/len].
|
||||
[split (->d ([fl (listof char?)])
|
||||
()
|
||||
(values [s (string-len/c (length fl))]
|
||||
[c (listof char?)]))])
|
||||
]
|
||||
Click on @scheme[string-len/c] to see what it does.
|
||||
|
||||
</question>
|
||||
@question[#:tag "no-domain"]{What about procedures of any specific arity?}
|
||||
|
||||
<question title="What about procedures of any specific arity?" tag="no-domain">
|
||||
<p>
|
||||
Imagine yourself writing a contract for a function that accepts some other
|
||||
function and a list of numbers that eventually applies the former to the
|
||||
latter. Unless the arity of the given function matches the length of the
|
||||
given list, your procedure is in trouble.
|
||||
</p>
|
||||
|
||||
|
||||
<p>Consider this @scheme[n-step] function:
|
||||
|
||||
<scheme>
|
||||
;; (Number ... -> (union #f number?)) (listof Number) -> Void
|
||||
Consider this @scheme[n-step] function:
|
||||
@schemeblock[
|
||||
(code:comment "(number ... -> (union #f number?)) (listof number) -> void")
|
||||
(define (n-step proc inits)
|
||||
(let ([inc (apply proc inits)])
|
||||
(when inc
|
||||
(n-step proc (map (λ (x) (+ x inc)) inits)))))
|
||||
</scheme>
|
||||
]
|
||||
|
||||
The argument of @scheme[n-step] is @scheme[proc], a function
|
||||
@scheme[proc] whose results are either numbers or false, and a list. It
|
||||
|
@ -467,122 +577,56 @@ then applies @scheme[proc] to the list @scheme[inits]. As long as
|
|||
@scheme[proc] returns a number, @scheme[n-step] treats that number
|
||||
as an increment for each of the numbers in @scheme[inits] and
|
||||
recurs. When @scheme[proc] returns @scheme[false], the loop stops.
|
||||
</p>
|
||||
|
||||
Here are two uses:
|
||||
<table>
|
||||
<tr>
|
||||
<td width="20" />
|
||||
<td valign="top">
|
||||
<pre>@scheme[
|
||||
;; Nat -> Nat
|
||||
@schemeblock[
|
||||
(code:comment "nat -> nat")
|
||||
(define (f x)
|
||||
(printf "~s \n" x)
|
||||
(if (= x 0) #f -1))
|
||||
|
||||
(n-step f '(2))
|
||||
]</pre></td>
|
||||
<td width="150" />
|
||||
<td valign="top" width="150"><pre>@scheme[
|
||||
;; Nat Nat -> Nat
|
||||
|
||||
(code:comment "nat nat -> nat")
|
||||
(define (g x y)
|
||||
(define z (+ x y))
|
||||
(printf "~s\n" (list x y z))
|
||||
(if (= z 0) #f -1))
|
||||
|
||||
(n-step g '(1 1))
|
||||
]</pre></td></tr>
|
||||
</table>
|
||||
</center>
|
||||
]
|
||||
|
||||
<p>A contract for @scheme[n-step] must specify two aspects of
|
||||
A contract for @scheme[n-step] must specify two aspects of
|
||||
@scheme[proc]'s behavior: its arity must include the number of elements
|
||||
in @scheme[inits], and it must return either a number of
|
||||
@scheme[#f]. The latter is easy, the former is difficult. At first
|
||||
glance, this appears to suggest a contract that assigns a
|
||||
<em>variable-arity</em> to @scheme[proc]:
|
||||
@scheme[
|
||||
@italic{variable-arity} to @scheme[proc]:
|
||||
@schemeblock[
|
||||
(->* ()
|
||||
(listof any/c)
|
||||
(or/c number? false/c))
|
||||
]
|
||||
This contract, however, says that the function must accept <em>any</em>
|
||||
number of arguments, not a <em>specific</em> but
|
||||
<em>undetermined</em> number. Thus, applying @scheme[n-step] to
|
||||
@italic{undetermined} number. Thus, applying @scheme[n-step] to
|
||||
@scheme[(lambda (x) x)] and @scheme[(list 1)] breaks the contract
|
||||
because the given function accepts only one argument.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The correct contract uses the @scheme[unconstrained-domain->]
|
||||
combinator, which specifies only the range of a function, not its
|
||||
domain. It is then possible to combine this contract with an arity test to
|
||||
specify the correct @scheme[n-step]'s contract:
|
||||
<scheme>
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
[n-step
|
||||
(->r ([proc (and/c (unconstrained-domain-> (or/c false/c number?))
|
||||
(λ (f) (procedure-arity-includes? f (length inits))))]
|
||||
(->d ([proc
|
||||
(and/c (unconstrained-domain->
|
||||
(or/c false/c number?))
|
||||
(λ (f) (procedure-arity-includes?
|
||||
f
|
||||
(length inits))))]
|
||||
[inits (listof number?)])
|
||||
()
|
||||
any)])
|
||||
</scheme>
|
||||
</p>
|
||||
]
|
||||
|
||||
</question>
|
||||
|
||||
<question title="What about opt-lambda?" tag="opt-lambda">
|
||||
|
||||
<p>Take a look at this excerpt from a string-processing module, inspired by the
|
||||
Scheme cookbook:
|
||||
<scheme>
|
||||
(module string-pad mzscheme
|
||||
(require (lib "contract.ss") (lib "etc.ss") (lib "string.ss" "srfi" "13"))
|
||||
|
||||
(provide/contract
|
||||
;; pad the given str left and right with
|
||||
;; the (optional) char so that it is centered
|
||||
[string-pad-center (opt-> (string? natural-number/c) (char?)
|
||||
string?)])
|
||||
|
||||
(define string-pad-center
|
||||
(opt-lambda (str width [pad #\space])
|
||||
(define field-width (min width (string-length str)))
|
||||
(define rmargin (- width (floor (/ (- width field-width) 2))))
|
||||
(string-pad (string-pad-right str rmargin pad) width pad))))
|
||||
</scheme>
|
||||
|
||||
The module exports @scheme[string-pad-center], a function that creates a
|
||||
string of a given @scheme[width] with the given string in the center. The
|
||||
default fill character is @scheme[#\space]; if the client module requires
|
||||
different character, it may call @scheme[string-pad-center] with a third
|
||||
argument, a @scheme[char], overwriting the default.
|
||||
|
||||
<p>The function definition uses @scheme[opt-lambda], which is appropriate
|
||||
for this kind of functionality. The interesting point here is the formulation of
|
||||
the contract for @scheme[opt-lambda]. Like the contract combinator for
|
||||
@scheme[->*], i.e., rest arguments, @scheme[opt->] demands several
|
||||
groups of contracts:
|
||||
<ol>
|
||||
<li>The first one is a parenthesized group of contracts for all required
|
||||
arguments. In this example, we see two: @scheme[string?] and
|
||||
@scheme[natural-number/c].
|
||||
|
||||
<li>The second one is a parenthesized group of contracts for all optional
|
||||
arguments: @scheme[char?].
|
||||
|
||||
<li>The last one is a single contract: the result of the function.
|
||||
</ol>
|
||||
Note if a default value does not satisfy a contract, you won't get a contract
|
||||
error for this interface. We do trust <em>you</em>; if you can't trust
|
||||
yourself, you need to communicate across boundaries for everything you write.
|
||||
|
||||
The contract library does not have built-in combinators to
|
||||
specify richer contracts for functions that have optional
|
||||
arguments, like functions that have optional arguments where
|
||||
the arguments depend on each other.
|
||||
|
||||
To specify such contracts combine @scheme[case->] with
|
||||
the other function contract combinators, like we did in
|
||||
the @scheme[substring1] function above.
|
||||
|
||||
}
|
|
@ -5,58 +5,52 @@
|
|||
@require["contracts-utils.ss"]
|
||||
@(require (for-label scheme/contract))
|
||||
|
||||
<section title="Gotchas" tag="gotchas" />
|
||||
@title{Gotchas}
|
||||
|
||||
<question> What about @scheme[set!] on variables provided via @scheme[provide/contract]?
|
||||
</question>
|
||||
@question{What about @scheme[set!] on variables provided via @scheme[provide/contract]?}
|
||||
|
||||
<p>
|
||||
The contract library assumes that variables exported
|
||||
via @scheme[provide/contract] are not assigned to, but
|
||||
does not enforce it. Accordingly, if you try
|
||||
to @scheme[set!] those variables, you may find
|
||||
unexpected behavior. As an example, consider this program:
|
||||
The contract library assumes that variables exported via
|
||||
@scheme[provide/contract] are not assigned to, but does not
|
||||
enforce it. Accordingly, if you try to @scheme[set!] those
|
||||
variables, you may find unexpected behavior. As an example,
|
||||
consider this program (running in the MzScheme language of
|
||||
DrScheme):
|
||||
|
||||
<scheme>
|
||||
(module x mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
@schemeblock[
|
||||
(module server scheme
|
||||
(define (inc-x!) (set! x (+ x 1)))
|
||||
(define x 0)
|
||||
(provide/contract [inc-x! (-> void?)]
|
||||
[x integer?]))
|
||||
|
||||
(module client mzscheme
|
||||
(require x)
|
||||
(module client scheme
|
||||
(require 'server)
|
||||
|
||||
(define (print-latest) (printf "x is ~s\n" x))
|
||||
|
||||
(print-latest)
|
||||
(inc-x!)
|
||||
(print-latest))
|
||||
|
||||
(require client)
|
||||
</scheme>
|
||||
(require 'client)
|
||||
]
|
||||
|
||||
When it runs, both calls to @scheme[print-latest]
|
||||
print @scheme[0], even though the value
|
||||
of @scheme[x] has been incremented (and the change is
|
||||
visible inside the module @scheme[x]).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To work around this, export accessor functions, rather than
|
||||
exporting the function directly, like this:
|
||||
<scheme>
|
||||
(module x mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
(define (get-x) x)
|
||||
(define (inc-x!) (set! x (+ x 1)))
|
||||
(define x 0)
|
||||
(provide/contract [inc-x! (-> void?)]
|
||||
[get-x (-> integer?)]))
|
||||
|
||||
</scheme>
|
||||
</p>
|
||||
@schememod[
|
||||
scheme
|
||||
|
||||
(define (get-x) x)
|
||||
(define (inc-x!) (set! x (+ x 1)))
|
||||
(define x 0)
|
||||
(provide/contract [inc-x! (-> void?)]
|
||||
[get-x (-> integer?)])
|
||||
]
|
||||
|
||||
<p>
|
||||
This is a bug we hope to address in a future release.
|
||||
</p>
|
||||
|
|
|
@ -20,9 +20,7 @@ boundaries. Specifically, programmers may attach contracts to
|
|||
@scheme[provide] clauses and thus impose constraints and promises on the use
|
||||
of exported values. For example, the export specification
|
||||
@schememod[
|
||||
scheme/base
|
||||
|
||||
(require scheme/contract) (code:comment "now we can write contracts")
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
[amount positive?])
|
||||
|
@ -34,13 +32,24 @@ positive number. The contract system monitors @scheme[a]'s obligation
|
|||
carefully. Every time a client refers to @scheme[amount], the monitor checks
|
||||
that the value of @scheme[amount] is indeed a positive number.
|
||||
|
||||
The contracts library is built into the Scheme language, but
|
||||
if you wish to use @scheme[scheme/base], you can explicitly
|
||||
require the contracts library like this:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract) (code:comment "now we can write contracts")
|
||||
|
||||
(provide/contract
|
||||
[amount positive?])
|
||||
(define amount ...)
|
||||
]
|
||||
|
||||
@question[#:tag "amount0"]{What happens if @scheme[a] sets @scheme[amount] to 0?}
|
||||
|
||||
Suppose the creator of @scheme[a] had written
|
||||
@schememod[
|
||||
scheme/base
|
||||
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
[amount positive?])
|
||||
|
@ -55,7 +64,7 @@ blame @scheme[a] for breaking its promises.
|
|||
|
||||
Suppose the creator of @scheme[a] had written
|
||||
@schememod[
|
||||
scheme/base
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
[amount positive?])
|
||||
|
@ -172,7 +181,7 @@ the module boundary for a second time.
|
|||
</question>
|
||||
}
|
||||
|
||||
@question[#:tag "obligations"]{How can a "server" module impose obligations on its client?}
|
||||
@question[#:tag "obligations"]{How can a ``server'' module impose obligations on its client?}
|
||||
|
||||
On occasion, a module may want to enter a contract with
|
||||
another module only if the other module abides by certain
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
@title[#:tag "contract-func"]{Simple Contracts on Functions}
|
||||
|
||||
When a module exports a function, it establishes two channels of
|
||||
communication between itself and the client module that imports the
|
||||
function. If the client module calls the function, it sends a value into the
|
||||
"server" module. Conversely, if such a function call ends and the function
|
||||
returns a value, the "server" module sends a value back to the "client" module.
|
||||
When a module exports a function, it establishes two
|
||||
channels of communication between itself and the client
|
||||
module that imports the function. If the client module calls
|
||||
the function, it sends a value into the ``server''
|
||||
module. Conversely, if such a function call ends and the
|
||||
function returns a value, the ``server'' module sends a
|
||||
value back to the "client" module.
|
||||
|
||||
It is important to keep this picture in mind when you read the explanations
|
||||
of the various ways of imposing contracts on functions.
|
||||
|
@ -23,8 +25,7 @@ select subset such as numbers, booleans, etc. Here is a module that may
|
|||
represent a bank account:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
[create (-> string? number? any)]
|
||||
|
@ -39,10 +40,10 @@ It exports two functions:
|
|||
@itemize{
|
||||
|
||||
@item{@scheme[create]: The function's contract says that it consumes two
|
||||
arguments, a string and a number, and it promises nothing about the return value. }}
|
||||
arguments, a string and a number, and it promises nothing about the return value. }
|
||||
|
||||
@item{@scheme[deposit]: The function's contract demands from the client modules
|
||||
that they apply it to numbers. It promises nothing about the return value. }
|
||||
that they apply it to numbers. It promises nothing about the return value. }}
|
||||
|
||||
If a "client" module that were to apply @scheme[deposit] to
|
||||
@scheme['silly], it would violate the contract. The contract monitoring
|
||||
|
@ -56,7 +57,7 @@ the contract monitoring system to check the return value every time the function
|
|||
is called, even though the "client" module can't do much with this value
|
||||
anyway. In contrast, @scheme[any] tells the monitoring system @italic{not}
|
||||
to check the return value. Additionally, it tells a potential client that the
|
||||
"server" module @italic{makes no promises at all} about the function's return
|
||||
``server'' module @italic{makes no promises at all} about the function's return
|
||||
value.
|
||||
|
||||
@question[#:tag "arrow"]{What does the arrow do?}
|
||||
|
@ -130,8 +131,7 @@ To this end, the contract system allows programmers to define their own
|
|||
contracts:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(define (amount? a)
|
||||
(and (number? a) (integer? a) (exact? a) (>= a 0)))
|
||||
|
@ -164,9 +164,7 @@ a contract defined in "contract.ss" that is equivalent to @scheme[amount]
|
|||
(modulo the name):
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(provide/contract
|
||||
(code:comment "an amount is a natural number of cents")
|
||||
|
@ -188,8 +186,7 @@ For example, if we didn't have @scheme[natural-number/c], the
|
|||
as follows:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(define amount
|
||||
(and/c number? integer? exact? (or/c positive? zero?)))
|
||||
|
@ -216,22 +213,24 @@ means? Hint: it is a contract!
|
|||
Consider a utility module for creating strings from banking records:
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
|
||||
scheme
|
||||
|
||||
(define (has-decimal? str)
|
||||
(define L (string-length str))
|
||||
(and (>= L 3)
|
||||
(char=?
|
||||
#\.
|
||||
(string-ref result (- L 3)))))
|
||||
|
||||
(provide/contract
|
||||
...
|
||||
(code:comment "convert a random number to a string")
|
||||
[format-number (-> number? string?)]
|
||||
|
||||
(code:comment "convert an amount into a dollar based string")
|
||||
[format-nat (-> natural-number/c
|
||||
(lambda (result)
|
||||
(define L (string-length result))
|
||||
(and (string? result)
|
||||
(>= L 3)
|
||||
(char=? #\. (string-ref result (- L 3))))))])
|
||||
...
|
||||
(has-decimal? string))))])
|
||||
]
|
||||
The contract of the exported function @scheme[format-number] specifies that
|
||||
the function consumes a number and produces a string.
|
||||
|
@ -241,19 +240,33 @@ interesting than the one of @scheme[format-number]. It consumes only
|
|||
natural numbers. Its range contract promises a string that has a dot "." in the
|
||||
third position from the right.
|
||||
|
||||
@bold{Exercise 1} Strengthen the promise of the range contract for
|
||||
@(exercise) Strengthen the promise of the range contract for
|
||||
@scheme[format-nat] so that it admits only strings with digits and a single
|
||||
dot.
|
||||
|
||||
@question[#:tag "exercise1"]{Solution to Exercise 1}
|
||||
@(solution)
|
||||
|
||||
@schememod[
|
||||
scheme/base
|
||||
(require scheme/contract)
|
||||
scheme
|
||||
|
||||
(define (digit-char? x)
|
||||
(member x '(#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0)))
|
||||
|
||||
|
||||
(define (has-decimal? str)
|
||||
(define L (string-length str))
|
||||
(and (>= L 3)
|
||||
(char=?
|
||||
#\.
|
||||
(string-ref result (- L 3)))))
|
||||
|
||||
(define (is-decimal-string? str)
|
||||
(define L (string-length str))
|
||||
(and (has-decimal? str)
|
||||
(andmap digit-char?
|
||||
(string->list (substring result 0 (- L 3))))
|
||||
(andmap digit-char?
|
||||
(string->list (substring result (- L 2) L)))))
|
||||
|
||||
(provide/contract
|
||||
...
|
||||
(code:comment "convert a random number to a string")
|
||||
|
@ -263,13 +276,8 @@ scheme/base
|
|||
(code:comment "into a dollar based string")
|
||||
[format-nat (-> natural-number/c
|
||||
(lambda (result)
|
||||
(define L (string-length result))
|
||||
(and (string? result)
|
||||
(andmap digit-char?
|
||||
(string->list (substring result 0 (- L 3))))
|
||||
(andmap digit-char?
|
||||
(string->list (substring result (- L 2) L)))
|
||||
(char=? #\. (string-ref result (- L 3))))))])
|
||||
(is-decimal-string? result))))])
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -5,67 +5,60 @@
|
|||
@require["contracts-utils.ss"]
|
||||
@(require (for-label scheme/contract))
|
||||
|
||||
<section title="Contracts on Structures" tag="structs" />
|
||||
@title{Contracts on Structures}
|
||||
|
||||
<p>Modules deal with structures in two ways. First they export
|
||||
@scheme[struct] definitions, i.e., the ability to create structs of a
|
||||
certain kind, to access their fields, to modify them, and to distinguish structs
|
||||
of this kind against every other kind of value in the world. Second, on occasion
|
||||
a module exports a specific struct and wishes to promise that its fields contain
|
||||
values of a certain kind. This section explains how to protect structs with
|
||||
contracts for both uses.
|
||||
Modules deal with structures in two ways. First they export
|
||||
@scheme[struct] definitions, i.e., the ability to create
|
||||
structs of a certain kind, to access their fields, to modify
|
||||
them, and to distinguish structs of this kind against every
|
||||
other kind of value in the world. Second, on occasion a
|
||||
module exports a specific struct and wishes to promise that
|
||||
its fields contain values of a certain kind. This section
|
||||
explains how to protect structs with contracts for both
|
||||
uses.
|
||||
|
||||
<question title="Can a module promise something about a specific struct?" tag="single-struct">
|
||||
@question[#:tag "single-struct"]{Can a module promise something about a specific struct?}
|
||||
|
||||
<p>Yes. If your module defines a variable to be a structure, then on export you
|
||||
Yes. If your module defines a variable to be a structure, then on export you
|
||||
can specify the structures shape:
|
||||
<scheme>
|
||||
(module geometry mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
(require posn)
|
||||
@schememod[
|
||||
scheme
|
||||
(require lang/posn)
|
||||
|
||||
(define origin (make-posn 0 0))
|
||||
...
|
||||
(define origin (make-posn 0 0))
|
||||
|
||||
(provide/contract
|
||||
[origin (struct/c posn zero? zero?)]
|
||||
...)
|
||||
... )
|
||||
</scheme>
|
||||
(provide/contract
|
||||
[origin (struct/c posn zero? zero?)])
|
||||
]
|
||||
|
||||
In this example, the module imports a library for representing positions, which
|
||||
exports a @scheme[posn] structure. One of the @scheme[posn]s it creates
|
||||
and exports stands for the origin, i.e., (0,), of the grid.
|
||||
In this example, the module imports a library for representing positions, which
|
||||
exports a @scheme[posn] structure. One of the @scheme[posn]s it creates
|
||||
and exports stands for the origin, i.e., @tt{(0,0)}, of the grid.
|
||||
|
||||
</question>
|
||||
@question[#:tag "single-vector"]{Can a module promise something about a specific vector?}
|
||||
|
||||
<question title="Can a module promise something about a specific vector?" tag="single-vector">
|
||||
|
||||
<p>Yes, again. See the help desk for information on @scheme[vector/c] and
|
||||
Yes, again. See the help desk for information on @scheme[vector/c] and
|
||||
similar contract combinators for (flat) compound data.
|
||||
|
||||
</question>
|
||||
@question[#:tag "define-struct"]{Can a contract enforce that all structs are well-formed?}
|
||||
|
||||
<question title="Can a contract enforce that all structs are well-formed?" tag="define-struct">
|
||||
The book @link["http://www.htdp.org/"]{@italic{How to Design
|
||||
Programs}} teaches that @scheme[posn]s should contain only
|
||||
numbers in their two fields. With contracts we would enforce
|
||||
this informal data definition as follows:
|
||||
|
||||
<p>"How to Design Programs" teaches that @scheme[posn]s should contain only
|
||||
numbers in their two fields. With contracts we would enforce this informal data
|
||||
definition as follows:
|
||||
|
||||
<scheme>
|
||||
(module posn mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
|
||||
(define-struct posn (x y))
|
||||
@schememod[
|
||||
scheme
|
||||
(define-struct posn (x y))
|
||||
|
||||
(provide/contract
|
||||
[struct posn ((x number?) (y number?))]
|
||||
[p-okay posn?]
|
||||
[p-sick posn?])
|
||||
(provide/contract
|
||||
[struct posn ((x number?) (y number?))]
|
||||
[p-okay posn?]
|
||||
[p-sick posn?])
|
||||
|
||||
(define p-okay (make-posn 10 20))
|
||||
(define p-sick (make-posn 'a 'b)))
|
||||
</scheme>
|
||||
(define p-okay (make-posn 10 20))
|
||||
(define p-sick (make-posn 'a 'b))
|
||||
]
|
||||
|
||||
This module exports the entire structure definition: @scheme[make-posn],
|
||||
@scheme[posn?], @scheme[posn-x], @scheme[posn-y],
|
||||
|
@ -73,117 +66,124 @@ This module exports the entire structure definition: @scheme[make-posn],
|
|||
or promises that the two fields of a @scheme[posn] structure are
|
||||
numbers---when the values flow across the module boundary.
|
||||
|
||||
<p>Thus, if a client calls @scheme[make-posn] on @scheme[10] and
|
||||
Thus, if a client calls @scheme[make-posn] on @scheme[10] and
|
||||
@scheme['a], the contract system will signal a contract
|
||||
violation. Similarly, if @scheme[(set-posn-x! (make-posn 10 10) 'a)] causes
|
||||
an error.
|
||||
violation.
|
||||
|
||||
<p>The creation of @scheme[p-sick] inside of the @scheme[posn] module,
|
||||
The creation of @scheme[p-sick] inside of the @scheme[posn] module,
|
||||
however, does not violate the contracts. The function @scheme[make-posn] is
|
||||
internal so @scheme['a] and @scheme['b] don't cross the module
|
||||
boundary. Similarly, when @scheme[p-sick] crosses the boundary of
|
||||
@scheme[posn], the contract promises a @scheme[posn?] and nothing
|
||||
else. In particular, this check does <em>not</em> enforce that the fields of
|
||||
else. In particular, this check does @italic{not} require that the fields of
|
||||
@scheme[p-sick] are numbers.
|
||||
|
||||
<p>The association of contract checking with module boundaries implies that
|
||||
The association of contract checking with module boundaries implies that
|
||||
@scheme[p-okay] and @scheme[p-sick] look alike from a client's
|
||||
perspective until the client extracts the pieces:
|
||||
|
||||
<scheme>
|
||||
(module client mzscheme
|
||||
(require posn)
|
||||
@schememod[
|
||||
scheme
|
||||
(require lang/posn)
|
||||
|
||||
... (posn-x p-sick) ...)
|
||||
</scheme>
|
||||
... (posn-x p-sick) ...
|
||||
]
|
||||
|
||||
Using @scheme[posn-x] is the only way @scheme[client] can find out what
|
||||
a @scheme[posn] contains in the @scheme[x] field. The application of
|
||||
@scheme[posn-x] sends @scheme[p-sick] back into the
|
||||
@scheme[posn]module and the result value -- @scheme['a] here -- back to
|
||||
the client, again across the module boundary. At this very point, the contract
|
||||
system discovers that a promise is broken. Specifically, @scheme[posn-x]
|
||||
doesn't return a number but a symbol and is therefore blamed.
|
||||
Using @scheme[posn-x] is the only way @scheme[client] can find out what
|
||||
a @scheme[posn] contains in the @scheme[x] field. The application of
|
||||
@scheme[posn-x] sends @scheme[p-sick] back into the
|
||||
@scheme[posn]module and the result value -- @scheme['a] here -- back to
|
||||
the client, again across the module boundary. At this very point, the contract
|
||||
system discovers that a promise is broken. Specifically, @scheme[posn-x]
|
||||
doesn't return a number but a symbol and is therefore blamed.
|
||||
|
||||
<p>This specific example shows that the explanation for a contract violation
|
||||
doesn't always pinpoint the source of the error. The good news is that the
|
||||
error is located in the @scheme[posn] module. The bad news is that the
|
||||
explanation is misleading. Although it is true that @scheme[posn-x]
|
||||
produced a symbol instead of a number, it is the fault of the programmer who
|
||||
created a @scheme[posn] from symbols, i.e., the programmer who added
|
||||
This specific example shows that the explanation for a contract violation
|
||||
doesn't always pinpoint the source of the error. The good news is that the
|
||||
error is located in the @scheme[posn] module. The bad news is that the
|
||||
explanation is misleading. Although it is true that @scheme[posn-x]
|
||||
produced a symbol instead of a number, it is the fault of the programmer who
|
||||
reated a @scheme[posn] from symbols, i.e., the programmer who added
|
||||
|
||||
<scheme>
|
||||
(define p-sick (make-posn 'a 'b))
|
||||
</scheme>
|
||||
@schemeblock[
|
||||
(define p-sick (make-posn 'a 'b))
|
||||
]
|
||||
to the module. So, when you are looking for bugs based on contract violations,
|
||||
keep this example in mind.
|
||||
|
||||
<p><strong>Exercise 2:</strong> Use your knowledge from the <a href="#single-struct">
|
||||
section on exporting specific structs</a> and change the contract for
|
||||
@scheme[p-sick] so that the error is caught when clients refer to the
|
||||
structure. <a href="#exercise2">Solution</a>
|
||||
@(exercise) Use your knowledge from the
|
||||
@questionlink["single-struct"] section on exporting specific
|
||||
structs and change the contract for @scheme[p-sick] so that
|
||||
the error is caught when @scheme[sick] is exported.
|
||||
|
||||
</question>
|
||||
@(solution)
|
||||
|
||||
<question title="What about contracts that check properties of data structures?" tag="lazy-contracts">
|
||||
A single change suffices:
|
||||
|
||||
@schemeblock[
|
||||
(provide/contract
|
||||
...
|
||||
[p-sick (struct/c posn number? number?)])
|
||||
]
|
||||
|
||||
Instead of exporting @scheme[p-sick] as a plain @scheme[posn?], we use a
|
||||
@scheme[struct/c] contract to enforce constraints on its components.
|
||||
|
||||
@question[#:tag "lazy-contracts"]{What about contracts that check properties of data structures?}
|
||||
|
||||
<p>
|
||||
Contracts written using @scheme[struct/c] immediately
|
||||
check the fields of the data structure, but sometimes this
|
||||
can have disastrous effects on the performance of a program
|
||||
that does not, itself, inspect the entire data structure.
|
||||
</p>
|
||||
|
||||
<p> As an example, consider the the binary search tree
|
||||
As an example, consider the the binary search tree
|
||||
search algorithm. A binary search tree is like a binary
|
||||
tree, except that the numbers are organized in the tree to
|
||||
make searching the tree fast. In particular, for each
|
||||
interior node in the tree, all of the numbers in the left
|
||||
subtree are smaller than the number in the node, and all of
|
||||
the numbers in the right subtree are larger than the number
|
||||
in the node. </p>
|
||||
in the node.
|
||||
|
||||
<p>
|
||||
We can implement implement a search
|
||||
function @scheme[in?] that takes advantage of the
|
||||
structure of the binary search tree.
|
||||
<scheme>
|
||||
(module bst mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
(define-struct node (val left right))
|
||||
@schememod[
|
||||
scheme
|
||||
|
||||
(define-struct node (val left right))
|
||||
|
||||
;; determines if `n' is in the binary search tree `b',
|
||||
;; exploiting the binary search tree invariant
|
||||
(define (in? n b)
|
||||
(cond
|
||||
[(null? b) #f]
|
||||
[else (cond
|
||||
[(= n (node-val b))
|
||||
#t]
|
||||
[(< n (node-val b))
|
||||
(in? n (node-left b))]
|
||||
[(> n (node-val b))
|
||||
(in? n (node-right b))])]))
|
||||
(code:comment "determines if `n' is in the binary search tree `b',")
|
||||
(code:comment "exploiting the binary search tree invariant")
|
||||
(define (in? n b)
|
||||
(cond
|
||||
[(null? b) #f]
|
||||
[else (cond
|
||||
[(= n (node-val b))
|
||||
#t]
|
||||
[(< n (node-val b))
|
||||
(in? n (node-left b))]
|
||||
[(> n (node-val b))
|
||||
(in? n (node-right b))])]))
|
||||
|
||||
(code:comment "a predicate that identifies binary search trees")
|
||||
(define (bst-between? b low high)
|
||||
(or (null? b)
|
||||
(and (<= low (node-val b) high)
|
||||
(bst-between? (node-left b) low (node-val b))
|
||||
(bst-between? (node-right b) (node-val b) high))))
|
||||
|
||||
(define (bst? b) (bst-between? b -inf.0 +inf.0))
|
||||
|
||||
;; a predicate that identifies binary search trees
|
||||
(define (bst-between? b low high)
|
||||
(or (null? b)
|
||||
(and (<= low (node-val b) high)
|
||||
(bst-between? (node-left b) low (node-val b))
|
||||
(bst-between? (node-right b) (node-val b) high))))
|
||||
|
||||
(define (bst? b) (bst-between? b -inf.0 +inf.0))
|
||||
|
||||
(provide (struct node (val left right)))
|
||||
(provide/contract
|
||||
[bst? (any/c . -> . boolean?)]
|
||||
[in? (number? bst? . -> . boolean?)]))
|
||||
</scheme>
|
||||
(provide (struct node (val left right)))
|
||||
(provide/contract
|
||||
[bst? (any/c . -> . boolean?)]
|
||||
[in? (number? bst? . -> . boolean?)])
|
||||
]
|
||||
|
||||
In a full binary search tree, this means that
|
||||
the @scheme[in?] function only has to explore a
|
||||
logarithmic number of nodes.
|
||||
</p>
|
||||
<p>
|
||||
|
||||
The contract on @scheme[in?] guarantees that its input
|
||||
is a binary search tree. But a little careful thought
|
||||
reveals that this contract defeats the purpose of the binary
|
||||
|
@ -195,18 +195,14 @@ recursive call. Now compare that to the @scheme[bst-between?]
|
|||
function. In the case that it returns @scheme[#t], it
|
||||
traverses the entire tree, meaning that the speedup
|
||||
of @scheme[in?] is lost.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In order to fix that, we can employ a new strategy for
|
||||
checking the binary search tree contract. In particular, if
|
||||
we only checked the contract on the nodes
|
||||
that @scheme[in?] looks at, we can still guarantee that
|
||||
the tree is at least partially well-formed, but without
|
||||
the performance loss.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To do that, we need to
|
||||
use @scheme[define-contract-struct] in place
|
||||
of @scheme[define-struct]. Like @scheme[define-struct],
|
||||
|
@ -217,9 +213,7 @@ defines contract combinators, in this
|
|||
case @scheme[node/c] and @scheme[node/dc]. Also unlike
|
||||
@scheme[define-struct], it does not define mutators, making
|
||||
its structs immutable.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The @scheme[node/c] function accepts a contract for each
|
||||
field of the struct and returns a contract on the
|
||||
struct. More interestingly, the syntactic
|
||||
|
@ -227,29 +221,31 @@ form @scheme[node/dc] allows us to write dependent
|
|||
contracts, i.e., contracts where some of the contracts on
|
||||
the fields depend on the values of other fields. We can use
|
||||
this to define the binary search tree contract:
|
||||
<scheme>
|
||||
(module bst mzscheme (require (lib "contract.ss"))
|
||||
(define-contract-struct node (val left right))
|
||||
@schememod[
|
||||
scheme
|
||||
|
||||
(define-contract-struct node (val left right))
|
||||
|
||||
;; determines if `n' is in the binary search tree `b'
|
||||
(define (in? n b) ... as before ...)
|
||||
(code:comment "determines if `n' is in the binary search tree `b'")
|
||||
(define (in? n b) ... as before ...)
|
||||
|
||||
;; bst-between : number number -> contract
|
||||
;; builds a contract for binary search trees
|
||||
;; whose values are betweeen low and high
|
||||
(define (bst-between/c low high)
|
||||
(or/c null?
|
||||
(node/dc [val (between/c low high)]
|
||||
[left (val) (bst-between/c low val)]
|
||||
[right (val) (bst-between/c val high)])))
|
||||
(code:comment "bst-between : number number -> contract")
|
||||
(code:comment "builds a contract for binary search trees")
|
||||
(code:comment "whose values are betweeen low and high")
|
||||
(define (bst-between/c low high)
|
||||
(or/c null?
|
||||
(node/dc [val (between/c low high)]
|
||||
[left (val) (bst-between/c low val)]
|
||||
[right (val) (bst-between/c val high)])))
|
||||
|
||||
(define bst/c (bst-between/c -inf.0 +inf.0))
|
||||
|
||||
(provide make-node node-left node-right node-val node?)
|
||||
(provide/contract
|
||||
[bst/c contract?]
|
||||
[in? (number? bst/c . -> . boolean?)]))
|
||||
</scheme>
|
||||
(define bst/c (bst-between/c -inf.0 +inf.0))
|
||||
|
||||
(provide make-node node-left node-right node-val node?)
|
||||
(provide/contract
|
||||
[bst/c contract?]
|
||||
[in? (number? bst/c . -> . boolean?)])
|
||||
]
|
||||
|
||||
In general, each use of @scheme[node/dc] must name the
|
||||
fields and then specify contracts for each fields. In the
|
||||
above, the @scheme[val] field is a contract that accepts
|
||||
|
@ -263,9 +259,7 @@ this contract ensures the same thing that
|
|||
the @scheme[bst-between?] function checked in the
|
||||
original example, but here the checking only happens
|
||||
as @scheme[in?] explores the tree.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Although this contract improves the performance
|
||||
of @scheme[in?], restoring it to the logarithmic
|
||||
behavior that the contract-less version had, it is still
|
||||
|
@ -274,40 +268,12 @@ library also provides @scheme[define-opt/c] that brings
|
|||
down that constant factor by optimizing its body. Its shape
|
||||
is just like the @scheme[define] above. It expects its
|
||||
body to be a contract and then optimizes that contract.
|
||||
<scheme>
|
||||
|
||||
@schemeblock[
|
||||
(define-opt/c (bst-between/c low high)
|
||||
(or/c null?
|
||||
(node/dc [val (between/c low high)]
|
||||
[left (val) (bst-between/c low val)]
|
||||
[right (val) (bst-between/c val high)])))
|
||||
</scheme>
|
||||
</p>
|
||||
</question>
|
||||
|
||||
<question title="Solution to Exercise 2" tag="exercise2">
|
||||
|
||||
<p>A single change suffices:
|
||||
|
||||
<scheme>
|
||||
(module posn mzscheme
|
||||
(require (lib "contract.ss"))
|
||||
|
||||
(define I (make-inspector))
|
||||
(provide I)
|
||||
|
||||
(define-struct posn (x y) I)
|
||||
|
||||
(provide/contract
|
||||
[struct posn ((x number?) (y number?))]
|
||||
[p-okay posn?]
|
||||
[p-sick <font color="red">(struct/c posn number? number?)</font>])
|
||||
|
||||
(define p-okay (make-posn 10 20))
|
||||
(define p-sick (make-posn 'a 'b)))
|
||||
</scheme>
|
||||
|
||||
Instead of exporting @scheme[p-sick] as a plain @scheme[posn?], we use a
|
||||
@scheme[struct/c] contract to enforce constraints on its components.
|
||||
|
||||
</question>
|
||||
]
|
||||
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
#lang scheme/base
|
||||
(require scribble/basic)
|
||||
(require scribble/basic
|
||||
scribble/manual)
|
||||
|
||||
(provide question)
|
||||
(provide question
|
||||
questionlink
|
||||
exercise
|
||||
solution)
|
||||
|
||||
(define (question #:tag [tag #f] . rest)
|
||||
(keyword-apply section
|
||||
'(#:tag)
|
||||
(list (and tag (format "contracts-~a" tag)))
|
||||
(list (and tag (str->tag tag)))
|
||||
rest))
|
||||
|
||||
(define (questionlink tag . rest) (apply seclink (str->tag tag) rest))
|
||||
|
||||
(define (str->tag tag) (format "contracts-~a" tag))
|
||||
|
||||
(define exercise-number 0)
|
||||
(define (exercise)
|
||||
(set! exercise-number (+ exercise-number 1))
|
||||
(bold (format "Exercise ~a" exercise-number)))
|
||||
|
||||
(define (solution)
|
||||
(bold (format "Solution to exercise ~a" exercise-number)))
|
|
@ -3,24 +3,39 @@
|
|||
@require[scribble/eval]
|
||||
@require["guide-utils.ss"]
|
||||
|
||||
@title[#:tag "contracts"]{Contracts}
|
||||
@title[#:tag "contracts" #:style 'toc]{Contracts}
|
||||
|
||||
@local-table-of-contents[]
|
||||
|
||||
@;{
|
||||
|
||||
Somewhere, discuss eq? and its impact on lists and
|
||||
procedures. Also, discuss difference between contracts on
|
||||
procedures.
|
||||
|
||||
Also, discuss difference between contracts on
|
||||
mutable datastructures & contracts on immutable ones.
|
||||
|
||||
Fill in question on optional arguments in general-function contracts.
|
||||
|
||||
->d and dependency (commented out section in general contracts).
|
||||
|
||||
update string-pad-center to show examples via REPL notation:
|
||||
|
||||
(string-pad-center "nba" 10)
|
||||
(code:comment "=> \" abc \"")
|
||||
|
||||
(string-pad-center "nba" 10 #\-)
|
||||
(code:comment "=> \"---abc----\"")
|
||||
|
||||
|
||||
}
|
||||
|
||||
@include-section["contracts-intro.scrbl"]
|
||||
@include-section["contracts-simple-function.scrbl"]
|
||||
@include-section["contracts-general-function.scrbl"]
|
||||
@;{
|
||||
@include-section["contracts-structure.scrbl"]
|
||||
@;{
|
||||
@include-section["contracts-class.scrbl"]
|
||||
@include-section["contracts-example.scrbl"]
|
||||
@include-section["contract-gotchas.scrbl"]
|
||||
}
|
||||
}
|
||||
@include-section["contracts-gotchas.scrbl"]
|
||||
|
|
|
@ -116,6 +116,10 @@ Like @scheme[</c], but for @scheme[<=].}
|
|||
@defproc[(>=/c [n number?]) flat-contract?]{
|
||||
Like @scheme[</c], but for @scheme[>=].}
|
||||
|
||||
@defproc[(between/c [n number?] [m number?])
|
||||
flat-contract?]{ Returns a flat contract that requires the
|
||||
input to be a between @scheme[n] and @scheme[m] or equal to
|
||||
one of them.}
|
||||
|
||||
@defproc[(real-in [n real?][m real?]) flat-contract?]{
|
||||
|
||||
|
@ -134,7 +138,7 @@ between @scheme[j] and @scheme[k], inclusive.}
|
|||
A flat contract that requires the input to be an exact non-negative integer.}
|
||||
|
||||
|
||||
@defproc[(string/len [len nonnegative-exact-integer?]) flat-contract?]{
|
||||
@defproc[(string-len/c [len nonnegative-exact-integer?]) flat-contract?]{
|
||||
|
||||
Returns a flat contract that recognizes strings that have fewer than
|
||||
@scheme[len] characters.}
|
||||
|
|
Loading…
Reference in New Issue
Block a user