diff --git a/collects/scribblings/guide/contracts-general-function.scrbl b/collects/scribblings/guide/contracts-general-function.scrbl index 00edbd6315..fc89bda77b 100644 --- a/collects/scribblings/guide/contracts-general-function.scrbl +++ b/collects/scribblings/guide/contracts-general-function.scrbl @@ -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 , 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 dependent 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. - -} - - -@;{ - - - -

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. -

Suppose the account module provides the following two functions: - -;; balance : Account -> Amount -;; withdraw : Account Amount -> Account - +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 -

-"the balance of the given account is greater than or equal to the given (withdrawal) amount." -
-The postcondition is similar to the one for @scheme[deposit]: -
-"the balance of the resulting account is larger than (or equal to) than the one of the -given account." -
+``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. -

The following module implements accounts imperatively and specifies the +The following module implements accounts imperatively and specifies the conditions we just discussed: - -
- -(module account mzscheme - (require (lib "contract.ss")) - - (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)) - - (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<))]) - - (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)) - - -
+(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 
-
-
-
- -The purple part is the export interface: -

    -
  1. @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). -
  2. - -
  3. @scheme[balance] consumes an account and computes its current balance. -
  4. - -
  5. @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. -

    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. -

  6. +} -
  7. @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. -
  8. -
+@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[#:tag "case-lambda"]{What about case-lambda?} - +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): -

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: - -(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: - -(provide/contract - [plus (->* (number? number?) (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 an additional - pair of parentheses: - -(number? number?) - - For @scheme[plus] they demand two numbers. The contract for the - rest argument follows: - -(listof number?) - - 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. - -

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. - - - - -

Dybvig explains - the meaning and pragmatics of @scheme[case-lambda] with the following - example (among others): - - +@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)])) - +] This version of @scheme[substring] has one of the following signature: -

    -
  1. just a string, in which case it copies the string; -
  2. a string and an index into the string, in which case it extracts the - suffix of the string starting at the index; or -
  3. a string a start index and an end index, in which case it extracts the - fragment of the string between the two indices. -
+@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. } +} -

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: - +@schemeblock[ (provide/contract - [substring1 (case-> - (string? . -> . string?) - (string? natural-number/c . -> . string?) - (string? natural-number/c natural-number/c . -> . string?))]) - + [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. -

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. Here we used @scheme[->r] to name the parameters and express the numeric constraints on them. - +} +@question[#:tag "multiple"]{What about multiple values?} - - -

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: - +@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 '())) - +] It is a typical multiple-value function, returning two values by traversing a single list. -

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: - +@schemeblock[ (provide/contract [split (-> (listof char?) (values string? (listof char?)))]) - -

+] -

The contract for such a function can also be written -using @scheme[->*], just like - @scheme[plus]: - +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?)))]) - - 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. -

+ () + (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. -

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: - + 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?))))]) - - 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. -

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: - +@schemeblock[ (provide/contract - [splitl (->d* ((listof char?)) - (lambda (fl) - (values (and/c string? (string/len (add1 (length fl)))) - (listof char?))))]) - - 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[#:tag "no-domain"]{What about procedures of any specific arity?} - -

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. -

- -

Consider this @scheme[n-step] function: - - -;; (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))))) - +] 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. -

Here are two uses: - - - - -
- -
@scheme[
-;; Nat -> Nat 
+@schemeblock[
+(code:comment "nat -> nat") 
 (define (f x)
   (printf "~s \n" x)
   (if (= x 0) #f -1))
-  
 (n-step f '(2))
-]
-
@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))
-]
- +] -

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 -variable-arity 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 any number of arguments, not a specific but -undetermined 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. -

-

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: - +@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)]) - -

+] -
- - - -

Take a look at this excerpt from a string-processing module, inspired by the -Scheme cookbook: - -(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)))) - - - 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 @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: -

    -
  1. 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]. - -
  2. The second one is a parenthesized group of contracts for all optional -arguments: @scheme[char?]. - -
  3. 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. - -} \ No newline at end of file diff --git a/collects/scribblings/guide/contracts-gotchas.scrbl b/collects/scribblings/guide/contracts-gotchas.scrbl index bb1496dce8..b6172e0b44 100644 --- a/collects/scribblings/guide/contracts-gotchas.scrbl +++ b/collects/scribblings/guide/contracts-gotchas.scrbl @@ -5,58 +5,52 @@ @require["contracts-utils.ss"] @(require (for-label scheme/contract)) -
+@title{Gotchas} - What about @scheme[set!] on variables provided via @scheme[provide/contract]? - +@question{What about @scheme[set!] on variables provided via @scheme[provide/contract]?} -

-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): - -(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) - +(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]). -

-

To work around this, export accessor functions, rather than exporting the function directly, like this: - -(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?)])) - -

+@schememod[ +scheme + +(define (get-x) x) +(define (inc-x!) (set! x (+ x 1))) +(define x 0) +(provide/contract [inc-x! (-> void?)] + [get-x (-> integer?)]) +] -

This is a bug we hope to address in a future release. -

diff --git a/collects/scribblings/guide/contracts-intro.scrbl b/collects/scribblings/guide/contracts-intro.scrbl index d559a7dcd7..01a022bb80 100644 --- a/collects/scribblings/guide/contracts-intro.scrbl +++ b/collects/scribblings/guide/contracts-intro.scrbl @@ -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[#: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 diff --git a/collects/scribblings/guide/contracts-simple-function.scrbl b/collects/scribblings/guide/contracts-simple-function.scrbl index bcf0e2947c..17fb2b58a5 100644 --- a/collects/scribblings/guide/contracts-simple-function.scrbl +++ b/collects/scribblings/guide/contracts-simple-function.scrbl @@ -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))))]) ] diff --git a/collects/scribblings/guide/contracts-structure.scrbl b/collects/scribblings/guide/contracts-structure.scrbl index afd6088e6a..29dbf34647 100644 --- a/collects/scribblings/guide/contracts-structure.scrbl +++ b/collects/scribblings/guide/contracts-structure.scrbl @@ -5,67 +5,60 @@ @require["contracts-utils.ss"] @(require (for-label scheme/contract)) -
+@title{Contracts on Structures} -

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[#:tag "single-struct"]{Can a module promise something about a specific struct?} -

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: - -(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?)] - ...) - ... ) - +(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[#:tag "single-vector"]{Can a module promise something about a specific vector?} - - -

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[#:tag "define-struct"]{Can a contract enforce that all structs are well-formed?} - +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: -

"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: - - -(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))) - +(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. -

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. -

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 not enforce that the fields of +else. In particular, this check does @italic{not} require that the fields of @scheme[p-sick] are numbers. -

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: - -(module client mzscheme - (require posn) +@schememod[ +scheme +(require lang/posn) - ... (posn-x p-sick) ...) - +... (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. -

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 - - (define p-sick (make-posn 'a 'b)) - +@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. -

Exercise 2: Use your knowledge from the -section on exporting specific structs and change the contract for -@scheme[p-sick] so that the error is caught when clients refer to the -structure. Solution +@(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. - +@(solution) - +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?} -

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. -

-

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.

+in the node. -

We can implement implement a search function @scheme[in?] that takes advantage of the structure of the binary search tree. - -(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?)])) - +(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. -

-

+ 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. -

-

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. -

-

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. -

-

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: - -(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?)])) - +(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. -

-

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. - + +@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)]))) - -

- - - - -

A single change suffices: - - -(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 (struct/c posn number? number?)]) - - (define p-okay (make-posn 10 20)) - (define p-sick (make-posn 'a 'b))) - - -Instead of exporting @scheme[p-sick] as a plain @scheme[posn?], we use a -@scheme[struct/c] contract to enforce constraints on its components. - - +] diff --git a/collects/scribblings/guide/contracts-utils.ss b/collects/scribblings/guide/contracts-utils.ss index 817a5a299c..99480bc90e 100644 --- a/collects/scribblings/guide/contracts-utils.ss +++ b/collects/scribblings/guide/contracts-utils.ss @@ -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))) \ No newline at end of file diff --git a/collects/scribblings/guide/contracts.scrbl b/collects/scribblings/guide/contracts.scrbl index e764ac6368..37aee7851e 100644 --- a/collects/scribblings/guide/contracts.scrbl +++ b/collects/scribblings/guide/contracts.scrbl @@ -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"] -} \ No newline at end of file +} +@include-section["contracts-gotchas.scrbl"] diff --git a/collects/scribblings/reference/contracts.scrbl b/collects/scribblings/reference/contracts.scrbl index 4f4adcfb49..025856ed38 100644 --- a/collects/scribblings/reference/contracts.scrbl +++ b/collects/scribblings/reference/contracts.scrbl @@ -116,6 +116,10 @@ Like @scheme[=/c [n number?]) flat-contract?]{ Like @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.}