racket/collects/scribblings/guide/set.scrbl
Eli Barzilay 4288c6c2c7 The Scribble reader was improved to make it pull out the syntax
punctuations outside of the form, as it does with quote punctuations.
So things like this

  #, @foo{...}

that required the space to make the @foo read as a scribble form are
now better written as

  @#,foo{...}

This changes all such occurrences.  (In case you see this change in
your files and are worried that there might be changes: I mechanically
verified that the result of `read'ing the modified files is identical
to the previous version.)

svn: r15111
2009-06-07 10:12:32 +00:00

196 lines
5.2 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss")
@(interaction-eval (require (lib "mzlib/for.ss")))
@title[#:tag "set!"]{Assignment: @scheme[set!]}
@refalso["set!"]{@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[#:tag "using-set!"]{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")
]]}
@;-- FIXME: explain more _why_ it's inferior
@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 than 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]}
@refalso["set!"]{@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 assigning 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)]