[Style] contract-out over provide/contract

This commit is contained in:
Matthias Felleisen 2012-12-27 17:27:26 -05:00 committed by Eli Barzilay
parent 9999a02009
commit ecb58309da
2 changed files with 58 additions and 31 deletions

View File

@ -22,7 +22,7 @@
;; bad ;; label a code fragment 'bad' [doesn't work] ;; bad ;; label a code fragment 'bad' [doesn't work]
column-table column-table
row-table row-table
rkt rkt/base rkt/gui) rkt rkt/base rkt/gui xml)
(define (LINEWIDTH) "102") (define (LINEWIDTH) "102")
@ -31,6 +31,7 @@
(define (rkt) (racketmodname racket)) (define (rkt) (racketmodname racket))
(define (rkt/base) (racketmodname racket/base)) (define (rkt/base) (racketmodname racket/base))
(define (rkt/gui) (racketmodname racket/gui)) (define (rkt/gui) (racketmodname racket/gui))
(define (xml) (racketmodname xml))
;; compare: two code snippets, in two columns: left is good, right is bad ;; compare: two code snippets, in two columns: left is good, right is bad
(define (compare stuff1 stuff2) (define (compare stuff1 stuff2)

View File

@ -55,6 +55,8 @@ If a unit of code looks incomprehensible, it is probably too large. Break
division; consider alternatives. division; consider alternatives.
@; ----------------------------------------------------------------------------- @; -----------------------------------------------------------------------------
@(define line
@t{---------------------------------------------------------------------------------------------------})
@section{Modules and their Interfaces} @section{Modules and their Interfaces}
The purpose of a module is to provide some services: The purpose of a module is to provide some services:
@ -64,7 +66,7 @@ The purpose of a module is to provide some services:
Often ``short'' means one line; occasionally you may need several lines. Often ``short'' means one line; occasionally you may need several lines.
In order to understand a module's services, organize the module in three In order to understand a module's services, organize the module in three
sections below the purpose statement: its imports, its exports, and its sections below the purpose statement: its exports, its imports, and its
implementation: implementation:
@;% @;%
@codebox[ @codebox[
@ -76,14 +78,17 @@ implementation:
;; the module implements a tv server ;; the module implements a tv server
(require 2htdp/universe htdp/image)
(provide (provide
;; launch the tv server function ;; launch the tv server function
tv-launch tv-launch
;; set up a tv client to receive messages from the tv server ;; set up a tv client to receive messages from the tv server
tv-client) tv-client)
(code:comment #, @line)
(code:comment #, @t{import and implementation section})
(require 2htdp/universe htdp/image)
(define (tv-launch) (define (tv-launch)
(universe ...)) (universe ...))
@ -92,9 +97,16 @@ implementation:
))] ))]
@;% @;%
If you choose to use @racket[provide/contract], define auxiliary concepts If you choose to use @racket[provide] with @racket[contract-out], you
related to the contracts between the @racket[require] and the may wish to have two @racket[require] sections:
@racket[provide] sections: @itemlist[
@item{the first one, placed above the @racket[provide] section, imports the
values needed to formulate the contracts and}
@item{the second one, placed below the @racket[provide] section, imports
the values needed to implement the services.}
]
If your contracts call for additional concepts, define those between the
@racket[require] for contracts and the @racket[provide] specification:
@;% @;%
@codebox[ @codebox[
@(begin @(begin
@ -105,7 +117,7 @@ If you choose to use @racket[provide/contract], define auxiliary concepts
;; the module implements a tv server ;; the module implements a tv server
(require 2htdp/universe htdp/image xml) (require xml)
(define player# 3) (define player# 3)
(define plain-board/c (define plain-board/c
@ -114,14 +126,20 @@ If you choose to use @racket[provide/contract], define auxiliary concepts
(define placement/c (define placement/c
(flat-named-contract "placement" ...)) (flat-named-contract "placement" ...))
(provide/contract (provide
(contract-out
;; initialize the board for the given number of players ;; initialize the board for the given number of players
[board-init (-> player#/c plain-board/c)] [board-init (-> player#/c plain-board/c)]
;; initialize a board and place the tiles ;; initialize a board and place the tiles
[create-board (-> player#/c (listof placement/c) [create-board (-> player#/c (listof placement/c)
(or/c plain-board/c string?))] (or/c plain-board/c string?))]
;; create a board from an X-expression representation ;; create a board from an X-expression representation
[board-deserialize (-> xexpr? plain-board/c)]) [board-deserialize (-> xexpr? plain-board/c)]))
(code:comment #, @line)
(code:comment #, @t{import and implementation section})
(require 2htdp/universe htdp/image)
; implementation: ; implementation:
(define (board-init n) (define (board-init n)
@ -135,8 +153,13 @@ If you choose to use @racket[provide/contract], define auxiliary concepts
(class ... some 900 lines ...)) (class ... some 900 lines ...))
))] ))]
@;% @;%
In the preceding code snippet, @xml[] imports the
@racket[xexpr?] predicate, which is needed to articulate the contract for
@racket[board-deserialize]. The @racket[require] line below the lines
imports an event-handling mechanism plus a simple image manipulation
library.
Avoid @racket[(provide (all-defined-out))]. Prefer specific export specifications over @racket[(provide (all-defined-out))].
A test suite section---if located within the module---should come at the A test suite section---if located within the module---should come at the
very end, including its specific dependencies, i.e., @racket[require] very end, including its specific dependencies, i.e., @racket[require]
@ -145,9 +168,9 @@ A test suite section---if located within the module---should come at the
@; ----------------------------------------------------------------------------- @; -----------------------------------------------------------------------------
@subsection{Require} @subsection{Require}
With @racket[require] specifications at the top of the module, you let With @racket[require] specifications at the top of the implementation
every reader know what is needed to understand the module. The section, you let every reader know what is needed to understand the
@racket[require] specification nails down the external dependencies. module.
@; ----------------------------------------------------------------------------- @; -----------------------------------------------------------------------------
@subsection{Provide} @subsection{Provide}
@ -169,7 +192,7 @@ This helps people find the relevant information quickly.
racket racket
;; This module implements ;; This module implements
;; several game strategies. ;; several strategies.
(require "game-basics.rkt") (require "game-basics.rkt")
@ -181,7 +204,7 @@ This helps people find the relevant information quickly.
human-strategy human-strategy
;; Stgy ;; Stgy
;; complete tree traversal ;; tree traversal
ai-strategy) ai-strategy)
(define (general p) (define (general p)
@ -202,7 +225,7 @@ This helps people find the relevant information quickly.
racket racket
;; This module implements ;; This module implements
;; several game strategies. ;; several strategies.
(require "game-basics.rkt") (require "game-basics.rkt")
@ -223,7 +246,7 @@ This helps people find the relevant information quickly.
(provide (provide
;; Stgy ;; Stgy
;; a complete tree traversal ;; a tree traversal
ai-strategy) ai-strategy)
(define ai-strategy (define ai-strategy
@ -261,8 +284,9 @@ racket
define-strategy) define-strategy)
))] ))]
Use @scheme[provide/contract] for module interfaces. Contracts often Use @scheme[provide] with @racket[contract-out] for module interfaces.
provide the right level of specification for first-time readers. Contracts often provide the right level of specification for first-time
readers.
At a minimum, you should use type-like contracts, i.e., predicates that At a minimum, you should use type-like contracts, i.e., predicates that
check for the constructor of data. They cost almost nothing, especially check for the constructor of data. They cost almost nothing, especially
@ -319,9 +343,10 @@ As of version 5.3, Racket supports sub-modules. Use sub-modules to
(module+ test (module+ test
(require rackunit)) (require rackunit))
(provide/contract (provide
(contract-out
(code:comment #, @t{convert a fahrenheit temperature to a celsius temperature}) (code:comment #, @t{convert a fahrenheit temperature to a celsius temperature})
[fahrenheit->celsius (-> number? number?)]) [fahrenheit->celsius (-> number? number?)]))
(define (fahrenheit->celsius f) (define (fahrenheit->celsius f)
(/ (* 5 (- f 32)) 9)) (/ (* 5 (- f 32)) 9))
@ -372,8 +397,9 @@ but the use of "module" in this phrase does @emph{not} only refer to
file-based or physical Racket modules. Clearly, @defterm{contract boundary} file-based or physical Racket modules. Clearly, @defterm{contract boundary}
is better than module boundary because it separates the two concepts. is better than module boundary because it separates the two concepts.
When you use @racket[provide/contract] at the module level, the boundary of When you use @racket[provide] with @racket[contract-out] at the module
the physical module and the contract boundary coincide. level, the boundary of the physical module and the contract boundary
coincide.
When a module becomes too large to manage without contracts but you do not When a module becomes too large to manage without contracts but you do not
wish to distribute the source over several files, you may wish to use one wish to distribute the source over several files, you may wish to use one