CSE 413 Autumn 2012 -- Side Effects in Racket

When To Use Side Effects in Racket?

Well, okay, that's a bit too extreme. For everything we do in this class the answer is never unless specifically told it's ok. But there are some cases where side effects are appropriate or necessary:

I/O

Racket includes various input and output statements, including terminal-based I/O and file I/O.

Some I/O functions:

read is a function that reads one Racket object from the standard input and returns it. (Note that this is thus a function with a side effect.)

Example:

(define clam (read))

There are some other functions to to read characters without parsing them into Racket objects.

write prints a representation of its argument to the standard output.

Example. This outputs a 7:

(write (+ 3 4))

Write is intended to print its output in a human-readable form, which can differ from the default output produced by Racket's read-eval-print loop. For instance, if we type '(a b c) we get

'(a b c)

But if we evaluate (write '(a b c)) we get

(a b c)

Multiple Forms

Many of the constructs we've discussed so far, including define, let, and cond, allow multiple expressions in their bodies. All but the last expression is evaluated just for its side effect(s) if any, and the value of the last expresson is the value of the surrounding construct. For example:

  (define (testit x)
  (write "this function doesn't do much")
  (newline)
  (write "but it does illustrate the point")
  (newline)
  (+ x x))

Here the write and newline expressions are evaluated (for their effect) and then the last expression (+ x x) is evaluated and its value returned as the value of the function. Similarly, there can be multiple forms in the body of a let, or the consequent part of a clause in a cond. However, if must have just a single form in its two arms. (Otherwise, how would Racket tell which was which?)

This capability hasn't been of any use in the past, since we haven't had any side effects -- clearly, the only time one would want to use this is if the forms other than the last have some side effect.


Assignment and List Surgery

Racket includes a special form for assignment:

 (set! var expr) 

Example:

(define x 10)
x
(set! x (+ x 1))
x

You can only use set! on a variable after it's been defined.

There is also a mutable version of cons cells in Racket to build mutable lists. The function mcons (instead of cons) builds a mutable cell. The functions set-mcar! and set-mcdr! change the car and cdr of the cell. See Mutable pairs and Lists in the Racket Guide for more details. There are additional functions like mlist and mlength that correspond to functions like list and length for ordinary lists. To use these, include (require racket/mpair) at the top of your code.

(In standard Scheme, you can change any cons cell and so do surgery on any list, using set-car! and set-cdr!. This was changed in recent versions of Racket.)

Example:

(define x (mcons 3 (mcons 2 null)))
x                                       ; prints (mcons 3 (mcons 2 '()))
(write x)                               ; prints {3 2}
(set-mcar! x 100)
x                                       ; prints (mcons 100 (mcons 2 '()))
(set-mcdr! x '())
x                                       ; prints (mcons 100 '())

Making a circular list:

(define circ (mcons 'a (mcons 'b '()))
(set-mcdr! (mcdr circ) circ)

When you pass a list to a function in Racket, a reference to the list is passed, rather than a copy. Thus, if you do any mutations on the list inside the function, the actual parameter will be affected.

(define (change-it x)
    (set-mcar! x 'frog))

(define animals (mcons 'squid (mcons 'octopus null)))
(change-it animals)

However, consider:

(define (test x)
   (write x)
   (set! x 100)
   (write x))

(define y '(1 2 3))
(test y)  ; prints (1 2 3) then 100
(write y) ; prints (1 2 3) ... y is unaltered