From e2dfc97726ae06937b0edeb63ac7e56e05be5866 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Tue, 19 Jun 2007 00:23:11 +0000 Subject: [PATCH] doc work: set! in guide svn: r6690 --- collects/scribble/eval.ss | 33 +++- collects/scribble/scheme.ss | 14 +- collects/scribblings/guide/begin.scrbl | 130 +++++++++++++ collects/scribblings/guide/forms.scrbl | 6 +- collects/scribblings/guide/set.scrbl | 190 +++++++++++++++++++ collects/scribblings/reference/derived.scrbl | 2 +- 6 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 collects/scribblings/guide/begin.scrbl create mode 100644 collects/scribblings/guide/set.scrbl diff --git a/collects/scribble/eval.ss b/collects/scribble/eval.ss index db4105e371..a31d011ef8 100644 --- a/collects/scribble/eval.ss +++ b/collects/scribble/eval.ss @@ -16,7 +16,9 @@ def+int defs+int examples + examples* defexamples + defexamples* as-examples current-int-namespace @@ -35,7 +37,7 @@ (make-table #f (append - (if title (list (list title)) null) + (if title (list (list (make-flow (list title)))) null) (let loop ([expr-paras expr-paras] [val-list+outputs val-list+outputs] [first? #t]) @@ -270,19 +272,36 @@ (interaction e ...)))])) (define example-title - (make-flow (list (make-paragraph (list "Examples:"))))) + (make-paragraph (list "Examples:"))) (define-syntax examples (syntax-rules () [(_ e ...) (titled-interaction example-title schemeinput* e ...)])) + (define-syntax examples* + (syntax-rules () + [(_ example-title e ...) + (titled-interaction example-title schemeinput* e ...)])) (define-syntax defexamples (syntax-rules () [(_ e ...) (titled-interaction example-title schemedefinput* e ...)])) + (define-syntax defexamples* + (syntax-rules () + [(_ example-title e ...) + (titled-interaction example-title schemedefinput* e ...)])) - (define (as-examples t) - (make-table #f - (list - (list example-title) - (list (make-flow (list t))))))) + (define (do-splice l) + (cond + [(null? l) null] + [(splice? (car l)) (append (splice-run (car l)) + (do-splice (cdr l)))] + [else (cons (car l) (do-splice (cdr l)))])) + (define as-examples + (case-lambda + [(t) (as-examples example-title t)] + [(example-title t) + (make-table #f + (list + (list (make-flow (list example-title))) + (list (make-flow (do-splice (list t))))))]))) diff --git a/collects/scribble/scheme.ss b/collects/scribble/scheme.ss index 34cfa4a279..d2dcdc2384 100644 --- a/collects/scribble/scheme.ss +++ b/collects/scribble/scheme.ss @@ -32,13 +32,13 @@ (define current-keyword-list ;; This is temporary, until the MzScheme manual is filled in... - (make-parameter '(define require provide - define-values begin0 when unless - new send if cond begin else => and or - define-syntax syntax-rules define-struct - quote quasiquote unquote unquote-splicing - syntax quasisyntax unsyntax unsyntax-splicing - set!))) + (make-parameter '(require + provide + new send else => and or + define-syntax syntax-rules define-struct + quote quasiquote unquote unquote-splicing + syntax quasisyntax unsyntax unsyntax-splicing + set! set!-values))) (define current-variable-list (make-parameter null)) (define current-meta-list diff --git a/collects/scribblings/guide/begin.scrbl b/collects/scribblings/guide/begin.scrbl new file mode 100644 index 0000000000..7120d9e925 --- /dev/null +++ b/collects/scribblings/guide/begin.scrbl @@ -0,0 +1,130 @@ +#reader(lib "docreader.ss" "scribble") +@require[(lib "manual.ss" "scribble")] +@require[(lib "eval.ss" "scribble")] +@require["guide-utils.ss"] + +@title[#:tag "guide:begin"]{Sequencing} + +Scheme programmers prefer to write programs with as few side-effects +as possible, since purely functional code is more easily tested and +composed into larger programs. Interaction with the external +environment, however, requires sequencing, such as when writing to a +display, opening a graphical window, or manipulating a file on disk. + +@;------------------------------------------------------------------------ +@section{Effects Before: @scheme[begin]} + +A @scheme[begin] expression sequences expressions: + +@specform[(begin expr ...+)]{} + +The @scheme[_expr]s are evaluated in order, and the result of all but +the last @scheme[_expr] is ignored. The result from the last +@scheme[_expr] is the result of the @scheme[begin] form, and it is in +tail position with respect to the @scheme[begin] form. + +@defexamples[ +(define (print-triangle height) + (if (zero? height) + (void) + (begin + (display (make-string height #\*)) + (newline) + (print-triangle (sub1 height))))) +(print-triangle 4) +] + +Many forms, such as @scheme[lambda] or @scheme[cond] support a +sequence of expressions even with a @scheme[begin]. Such positions are +sometimes said to have an @defterm{implicit begin}. + +@defexamples[ +(define (print-triangle height) + (cond + [(not (positive? height)) + (display (make-string height #\*)) + (newline) + (print-triangle (sub1 height))])) +(print-triangle 4) +] + +The @scheme[begin] form is special at the top level, at module level, +or as a @scheme[body] after only internal definitions. In those +positions, instead of forming an expression, the content of +@scheme[begin] is spliced into the surrounding context. + +@defexamples[ +(let ([curly 0]) + (begin + (define moe (+ 1 curly)) + (define larry (+ 1 moe))) + (list larry curly moe)) +] + +This splicing behavior is mainly useful for macros, as we discuss +later in @secref["guide:macros"]. + +@;------------------------------------------------------------------------ +@section{Effects After: @scheme[begin0]} + +A @scheme[begin0] expression has the same syntax as a @scheme[begin] +expression: + +@specform[(begin0 expr ...+)]{} + +The difference is that @scheme[begin0] returns the result of the first +@scheme[expr], instead of the result of the last @scheme[expr]. The +@scheme[begin0] form is useful for implementing side-effects that +happen after a computation, especially in the case where the +computation produces an unknown number of results. + +@defexamples[ +(define (log-times thunk) + (printf "Start: ~s\n" (current-inexact-milliseconds)) + (begin0 + (thunk) + (printf "End..: ~s\n" (current-inexact-milliseconds)))) +(log-times (lambda () (sleep 0.1) 0)) +(log-times (lambda () (values 1 2))) +] + +@;------------------------------------------------------------------------ +@section{Effects If...: @scheme[when] and @scheme[unless]} + +The @scheme[when] form combines an @scheme[if]-style conditional with +sequencing for the ``then'' clause and no ``else'' clause: + +@specform[(when test-expr then-expr ...)] + +If @scheme[_test-expr] produces a true value, then all of the +@scheme[_then-expr]s are evaluated. Otherwise, no @scheme[_then-expr]s +are evaluated. The result is @|void-const| in any case. + +The @scheme[unless] form is similar: + +@specform[(unless test-expr then-expr ...)] + +The difference is that the @scheme[_test-expr] result is inverted: the +@scheme[_then-expr]s are evaluated only if the @scheme[_test-expr] +result is @scheme[#f]. + +@defexamples[ +(define (enumerate lst) + (if (null? (cdr lst)) + (printf "~a.\n" (car lst)) + (begin + (printf "~a, " (car lst)) + (when (null? (cdr (cdr lst))) + (printf "and ")) + (enumerate (cdr lst))))) +(enumerate '("Larry" "Curly" "Moe")) +] + +@def+int[ +(define (print-triangle height) + (unless (zero? height) + (display (make-string height #\*)) + (newline) + (print-triangle (sub1 height)))) +(print-triangle 4) +] diff --git a/collects/scribblings/guide/forms.scrbl b/collects/scribblings/guide/forms.scrbl index f5ffa6f8ba..958eec1e61 100644 --- a/collects/scribblings/guide/forms.scrbl +++ b/collects/scribblings/guide/forms.scrbl @@ -85,10 +85,8 @@ form, a @scheme[_thing] is either an identifier or a keyword. @include-section["define.scrbl"] @include-section["let.scrbl"] @include-section["cond.scrbl"] - -@section[#:tag "guide:begin"]{Sequencing: @scheme[begin], @scheme[begin0], @scheme[when], and @scheme[unless]} - -@section{Assignment: @scheme[set!]} +@include-section["begin.scrbl"] +@include-section["set.scrbl"] @section{Quoted Data: @scheme[quote] and @scheme[quasiquote]} diff --git a/collects/scribblings/guide/set.scrbl b/collects/scribblings/guide/set.scrbl new file mode 100644 index 0000000000..a58a211691 --- /dev/null +++ b/collects/scribblings/guide/set.scrbl @@ -0,0 +1,190 @@ +#reader(lib "docreader.ss" "scribble") +@require[(lib "manual.ss" "scribble")] +@require[(lib "eval.ss" "scribble")] +@require["guide-utils.ss"] + +@interaction-eval[(require (lib "for.ss"))] + +@title[#:tag "guide:set!"]{Assignment: @scheme[set!]} + +Assign to a variable using @scheme[set!]: + +@specform[(set! id expr)] + +A @scheme[set!] expression evaluates @scheme[_expr] and changes +@scheme[_id] (which must be bound in the enclosing environment) to the +resulting value. The result of the @scheme[set!] expression itself is +@|void-const|. + +@defexamples[ +(define greeted null) +(define (greet name) + (set! greeted (cons name greeted)) + (string-append "Hello, " name)) + +(greet "Athos") +(greet "Porthos") +(greet "Aramis") +greeted +] + +@defs+int[ +[(define (make-running-total) + (let ([n 0]) + (lambda () + (set! n (+ n 1)) + n))) + (define win (make-running-total)) + (define lose (make-running-total))] +(win) +(win) +(lose) +(win) +] + +@;------------------------------------------------------------------------ +@section{Guidelines for Using Assignment} + +Although using @scheme[set!] is sometimes appropriate, Scheme style +generally discourages the use of @scheme[set!]. The following +guidelines may help explain when using @scheme[set!] is appropriate. + +@itemize{ + + @item{As in any modern language, assigning to shared identifier is no + substitute for passing an argument to a procedure or getting + its result. + + @as-examples[@t{@bold{@italic{Really awful}} example:} + @defs+int[ + [(define name "unknown") + (define result "unknown") + (define (greet) + (set! result (string-append "Hello, " name)))] + (set! name "John") + (greet) + result + ]] + + @as-examples[@t{Ok example:} + @def+int[ + (define (greet name) + (string-append "Hello, " name)) + (greet "John") + (greet "Anna") + ]]} + + @item{A sequence of assignments to a local variable is far inferior + to nested bindings. + + @as-examples[@t{@bold{Bad} example:} + @interaction[ + (let ([tree 0]) + (set! tree (list tree 1 tree)) + (set! tree (list tree 2 tree)) + (set! tree (list tree 3 tree)) + tree)]] + + @as-examples[@t{Ok example:} + @interaction[ + (let* ([tree 0] + [tree (list tree 1 tree)] + [tree (list tree 2 tree)] + [tree (list tree 3 tree)]) + tree)]]} + + @item{Using assignment to accumulate results from an iteration is + bad style. Accumulating through a loop argument is better. + + @as-examples[@t{Somewhat bad example:} + @def+int[ + (define (sum lst) + (let ([s 0]) + (for-each (lambda (i) (set! s (+ i s))) + lst) + s)) + (sum '(1 2 3)) + ]] + + @as-examples[@t{Ok example:} + @def+int[ + (define (sum lst) + (let loop ([lst lst][s 0]) + (if (null? lst) + s + (loop (cdr lst) (+ s (car lst)))))) + (sum '(1 2 3)) + ]] + + @as-examples[@t{Better (use an existing function) example:} + @def+int[ + (define (sum lst) + (apply + lst)) + (sum '(1 2 3)) + ]] + + @as-examples[@t{Good (a general approach) example:} + @def+int[ + (define (sum lst) + (for/fold ([s 0]) + ([i (in-list lst)]) + (+ s i))) + (sum '(1 2 3)) + ]] } + + @item{For cases where stateful objects are necessary or appropriate, + then implementing the object's state with @scheme[set!] is + fine. + + @as-examples[@t{Ok example:} + @def+int[ + (define next-number! + (let ([n 0]) + (lambda () + (set! n (add1 n)) + n))) + (next-number!) + (next-number!) + (next-number!)]]} + +} + +All else being equal, a program that uses no assignments or mutation +is always preferable to one that uses assignments or mutation. While +side effects are to be avoided, however, they should be used if the +resulting code is significantly more readable or if it implements a +significantly better algorithm. + +The use of mutable values, such as vectors and hash tables, raises +fewer suspicions about the style of a program that using @scheme[set!] +directly. Nevertheless, simply replacing @scheme[set!]s in a program +with a @scheme[vector-set!]s obviously does not improve the style of +the program. + +@;------------------------------------------------------------------------ +@section{Multiple Values: @scheme[set!-values]} + +The @scheme[set!-values] form assigns to multiple variables at once, +given an expression that produces an appropriate number of values: + +@specform[(set!-values (id ...) expr)] + +This form is equivalent to using @scheme[let-values] to receive +multiple results from @scheme[_expr], and then assign the results +individually to the @scheme[_id]s using @scheme[set!]. + +@defexamples[ +(define game + (let ([w 0] + [l 0]) + (lambda (win?) + (if win? + (set! w (+ w 1)) + (set! l (+ l 1))) + (begin0 + (values w l) + (code:comment #, @t{swap sides...}) + (set!-values (w l) (values l w)))))) +(game #t) +(game #t) +(game #f)] diff --git a/collects/scribblings/reference/derived.scrbl b/collects/scribblings/reference/derived.scrbl index d4f9f4b1eb..058b1a241d 100644 --- a/collects/scribblings/reference/derived.scrbl +++ b/collects/scribblings/reference/derived.scrbl @@ -116,7 +116,7 @@ position with respect to the original @scheme[or] form. @;------------------------------------------------------------------------ @section{Guarded Evaluation: @scheme[when] and @scheme[unless]} -@defform[(while test-expr expr ...)]{ +@defform[(when test-expr expr ...)]{ Evaluates the @scheme[text-expr]. If the result is any value other than @scheme[#f], the @scheme[expr]s are evaluated, and the results