196 lines
5.2 KiB
Racket
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)]
|