From d37158bd7663edd020a4a6444a05ec067aa60d69 Mon Sep 17 00:00:00 2001 From: Asumu Takikawa Date: Sat, 8 Feb 2014 21:27:02 -0500 Subject: [PATCH] Add initial prose for define/contract in the Guide This commit adds a few sections that explain some of the subtleties of nested contract boundaries. --- .../scribblings/guide/contracts-gotchas.scrbl | 36 +++++++++++++++++++ .../scribblings/guide/contracts-intro.scrbl | 27 ++++++++++++++ .../guide/contracts-simple-function.scrbl | 30 ++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-gotchas.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-gotchas.scrbl index 55dd201a63..44d1ed5b84 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-gotchas.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-gotchas.scrbl @@ -153,3 +153,39 @@ racket Moral: This is a bug that we will address in a future release. +@ctc-section[#:tag "gotcha-nested"]{Contract boundaries established by @racket[define/contract]} + +The contract boundaries established by @racket[define/contract], which +creates a nested contract boundary, is sometimes unintuitive. This is +especially true when multiple functions or other values with contracts +interact. For example, consider these two interacting functions: + +@(define e2 (make-base-eval)) +@(interaction-eval #:eval e2 (require racket/contract)) +@interaction[#:eval e2 +(define/contract (f x) + (-> integer? integer?) + x) +(define/contract (g) + (-> string?) + (f "not an integer")) +(g) +] +@(close-eval e2) + +One may expect that the function @racket[g] will be blamed +for breaking the terms of its contract with @racket[f]. Instead, the +``top-level'' is blamed. + +The reason that the ``top-level'' is blamed is because there are two +contract boundaries in play and none of them are directly between +@racket[f] and @racket[g]. Both @racket[f] and @racket[g] establish +boundaries between themselves and their context, i.e., the top-level. +In order for @racket[f] to be called by @racket[g], the arguments +that are supplied by @racket[g] pass through the top-level, which +takes responsibility for the values ultimately supplied to @racket[f]. +Thus, it makes sense that the top-level is blamed. + +Moral: if two values with contracts should interact, + consider putting them in separate modules with contracts at + the module boundary. \ No newline at end of file diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-intro.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-intro.scrbl index 2914091dba..fe9a823b5e 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-intro.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-intro.scrbl @@ -210,3 +210,30 @@ with the @racket[module+] keyword at the front. The first form after @racket[module] is the name of the module to be used in a subsequent @racket[require] statement (where each reference through a @racket[require] prefixes the name with @racket[".."]). + +@ctc-section[#:tag "intro-nested"]{Experimenting with Nested Contract Boundaries} + +In many cases, it makes sense to attach contracts at module boundaries. +It is often convenient, however, to be able to use contracts at +a finer granularity than modules. The @racket[define/contract] +form enables this kind of use: + +@racketmod[ +racket + +(define/contract amount + (and/c number? positive?) + 150) + +(+ amount 10) +] + +In this example, the @racket[define/contract] form establishes a contract +boundary between the definition of @racket[amount] and its surrounding +context. In other words, the two parties here are the definition and +the module that contains it. + +Forms that create these @emph{nested contract boundaries} can sometimes +be subtle to use because they may have unexpected performance implications +or blame a party that may seem unintuitive. These subtleties are explained +in @secref["simple-nested"] and @ctc-link["gotcha-nested"]. diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-simple-function.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-simple-function.scrbl index 3767ea1203..101dcc0a05 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-simple-function.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts-simple-function.scrbl @@ -100,6 +100,36 @@ is just another way of writing (-> number? any) ] +@; ------------------------------------------------------------------------ + +@section[#:tag "simple-nested"]{Using @racket[define/contract] and @racket[->]} + +The @racket[define/contract] form introduced in @ctc-link["intro-nested"] can +also be used to define functions that come with a contract. For example, + +@racketblock[ +(define/contract (deposit amount) + (-> number? any) + (code:comment "implementation goes here") + ....) +] + +which defines the @racket[deposit] function with the contract from earlier. +Note that this has two potentially important impacts on the use of +@racket[deposit]: + +@itemlist[#:style "ordered" + @item{Since the contract will always be checked on calls to @racket[deposit], + even inside the module in which it is defined, this may increase + the number of times the contract is checked. This could lead to + a performance degradation. This is especially true if the function + is called repeatedly in loops or recursion.} + @item{In some situations, a function may be written to accept a more + lax set of inputs when called by other code in the same module. + For such use cases, the contract boundary established by + @racket[define/contract] is too strict.} +] + @; ---------------------------------------------------------------------- @section{@racket[any] and @racket[any/c]}