4.9 Assignment: set!

► |  | The Racket Guide

1 | Welcome to Racket
2 | Racket Essentials
3 | Built-In Datatypes
4 | Expressions and Definitions
5 | Programmer-Defined Datatypes
6 | Modules
7 | Contracts
8 | Input and Output
9 | Regular Expressions
10 | Exceptions and Control
11 | Iterations and Comprehensions
12 | Pattern Matching
13 | Classes and Objects
14 | Units
15 | Reflection and Dynamic Evaluation
16 | Macros
17 | Creating Languages
18 | Concurrency and Synchronization
19 | Performance
20 | Parallelism
21 | Running and Creating Executables
22 | More Libraries
23 | Dialects of Racket and Scheme
24 | Command-Line Tools and Your Editor of Choice
| Bibliography
| Index

▼ | 4 | Expressions and Definitions

4.1 | Notation
4.2 | Identifiers and Binding
4.3 | Function Calls
4.4 | Functions: lambda
4.5 | Definitions: define
4.6 | Local Binding
4.7 | Conditionals
4.8 | Sequencing
4.9 | Assignment: set!
4.10 | Quoting: quote and '
4.11 | Quasiquoting: quasiquote and ‘
4.12 | Simple Dispatch: case
4.13 | Dynamic Binding: parameterize

► | 4.9 | Assignment: set!

4.9.1 | Guidelines for Using Assignment
4.9.2 | Multiple Values: set!-values

On this page:

4.9.1`` Guidelines for Using Assignment
4.9.2`` Multiple Values: set!-values

top ← prev up next →

#### 4.9`` Assignment: set!

> > > Assignment: set! and set!-values in The Racket Reference also documents set!.

Assign to a variable using set!:

> > (set! id expr)

A set! expression evaluates expr and changes id (which must be bound in the enclosing environment) to the resulting value. The result of the set! expression itself is #<void>.

Examples:

> (define greeted null)
> (define (greet name)
> (set! greeted (cons name greeted))
> (string-append "Hello, " name))
> > (greet "Athos")
> "Hello, Athos"
> > (greet "Porthos")
> "Hello, Porthos"
> > (greet "Aramis")
> "Hello, Aramis"
> > greeted
> '("Aramis" "Porthos" "Athos")

> (define (make-running-total)
> (let ([n 0])
> (lambda ()
> (set! n (+ n 1))
> n)))
> (define win (make-running-total))
> (define lose (make-running-total))
> > (win)
> 1
> > (win)
> 2
> > (lose)
> 1
> > (win)
> 3

##### 4.9.1`` Guidelines for Using Assignment

Although using set! is sometimes appropriate, Racket style generally discourages the use of set!. The following guidelines may help explain when using set! is appropriate.

- As in any modern language, assigning to a shared identifier is no substitute for passing an argument to a procedure or getting its result.

  Really awful example:

  > (define name "unknown")
  > (define result "unknown")
  > (define (greet)
  > (set! result (string-append "Hello, " name)))
  > > (set! name "John")
  > > (greet)
  > > result
  > "Hello, John"

  Ok example:

  > (define (greet name)
  > (string-append "Hello, " name))
  > > (greet "John")
  > "Hello, John"
  > > (greet "Anna")
  > "Hello, Anna"

- A sequence of assignments to a local variable is far inferior to nested bindings.

  Bad example:

  > > (let ([tree 0])
  > (set! tree (list tree 1 tree))
  > (set! tree (list tree 2 tree))
  > (set! tree (list tree 3 tree))
  > tree)
  > '(((0 1 0) 2 (0 1 0)) 3 ((0 1 0) 2 (0 1 0)))

  Ok example:

  > > (let* ([tree 0]
  > [tree (list tree 1 tree)]
  > [tree (list tree 2 tree)]
  > [tree (list tree 3 tree)])
  > tree)
  > '(((0 1 0) 2 (0 1 0)) 3 ((0 1 0) 2 (0 1 0)))

- Using assignment to accumulate results from an iteration is bad style. Accumulating through a loop argument is better.

  Somewhat bad example:

  > (define (sum lst)
  > (let ([s 0])
  > (for-each (lambda (i) (set! s (+ i s)))
  > lst)
  > s))
  > > (sum '(1 2 3))
  > 6

  Ok example:

  > (define (sum lst)
  > (let loop ([lst lst] [s 0])
  > (if (null? lst)
  > s
  > (loop (cdr lst) (+ s (car lst))))))
  > > (sum '(1 2 3))
  > 6

  Better (use an existing function) example:

  > (define (sum lst)
  > (apply + lst))
  > > (sum '(1 2 3))
  > 6

  Good (a general approach) example:

  > (define (sum lst)
  > (for/fold ([s 0])
  > ([i (in-list lst)])
  > (+ s i)))
  > > (sum '(1 2 3))
  > 6

- For cases where stateful objects are necessary or appropriate, then implementing the object’s state with set! is fine.

  Ok example:

  > (define next-number!
  > (let ([n 0])
  > (lambda ()
  > (set! n (add1 n))
  > n)))
  > > (next-number!)
  > 1
  > > (next-number!)
  > 2
  > > (next-number!)
  > 3

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 set! directly. Nevertheless, simply replacing set!s in a program with vector-set!s obviously does not improve the style of the program.

##### 4.9.2`` Multiple Values: set!-values

> > > Assignment: set! and set!-values in The Racket Reference also documents set!-values.

The set!-values form assigns to multiple variables at once, given an expression that produces an appropriate number of values:

> > (set!-values (id ...) expr)

This form is equivalent to using let-values to receive multiple results from expr, and then assigning the results individually to the ids using set!.

Examples:

> (define game
> (let ([w 0]
> [l 0])
> (lambda (win?)
> (if win?
> (set! w (+ w 1))
> (set! l (+ l 1)))
> (begin0
> (values w l)
> ; swap sides...
> (set!-values (w l) (values l w))))))
> > (game #t)
> 1
> 0
> > (game #t)
> 1
> 1
> > (game #f)
> 1
> 2

top ← prev up next →