CSE 341 - Side Effects in Scheme

When To Use Side Effects in Scheme?


Scheme 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 Scheme object from the standard input and returns it. (Note that this is thus a function with a side effect.)


(define clam (read))
There are some other functions to to read characters without parsing them into Scheme objects.

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

Example. This outputs a 7:

(write (+ 3 4))
display is similar to write, but omits quotes around strings.

Multiple Forms

Many of the constructs we've discussed so far, including define, let, and cond, allow multiple forms in their bodies. All but the last form is evaluated just for its side effect, and the value of the last form is used. For example:
(define (testit x)
  (write "this function doesn't do much")
  (write "but it does illustrate the point")
  (+ 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 Scheme tell which was which?)

The only time one would want to use this is if the forms other than the last have some side effect! If you need to, you can also wrap multiple expressions into one using

(begin ...)

Assignment and List Surgery

Scheme includes a special form for assignment:
 (set! var expr) 
(define x 10)
(write x)
(set! x (+ x 1))
(write x)
There are also functions set-car! and set-cdr! to assign into the car or cdr of a cons cell. Finally, there is a special form do for iteration.


(define x '(1 2 3 4))
(write x)     ; prints (1 2 3 4)
(set-car! x 100)
(write x)     ; prints (100 2 3 4)
(set-cdr! x ())
(write x)     ; prints  (100)
Making a circular list:
(define circ '(a b))
(set-cdr! (cdr circ) circ)
When you pass a list to a function in Scheme, a reference to the list is passed, rather than a copy. Thus, if you use set-car! or set-cdr! to do list surgery inside the function, the actual parameter will be affected.
(define (change-it x)
    (set-car! x 'frog))

(define animals '(rabbit horse squirrel monkey))
(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

Scope and Side Effects

We can simulate object-oriented programming in Scheme using a combination of lexical scoping and side effects.

First, let's see how functions can share state.

(define incr ())  ; just define the variable incr with a dummy value for now
(define get ())

(let ((n 0))
   (set! incr (lambda (i) (set! n (+ n i))))
   (set! get (lambda () n)))

The variables incr and get are global variables, now bound to functions. The variable n is shared by them, but is hidden -- it is not a global variable.

Now try evaluating the following expressions:


(incr 10)

(incr 100)
Now we can add dispatching to simulate a simple object. (This example is adapted from Structure and Interpretation of Computer Programs.) Here we define a bank account object, with a field my-balance, and methods balance, deposit, and withdraw.

(downloadable version of bank account code )

(define (make-account)
   (let ((my-balance 0))

      ;; return the current balance
      (define (balance)

      ;; make a withdrawal
     (define (withdraw amount)
        (if (>= my-balance amount)
           (begin (set! my-balance (- my-balance amount))
           "Insufficient funds"))

     ;; make a deposit
     (define (deposit amount)
        (set! my-balance (+ my-balance amount))

     ;; the dispatching function -- decide what to do with the request
     (define (dispatch m)
        (cond ((eq? m 'balance) balance)
              ((eq? m 'withdraw) withdraw)
              ((eq? m 'deposit) deposit)
              (else (error "Unknown request -- MAKE-ACCOUNT"  m))))

Note that the variable my-balance is local to the make-account function.

Using the account:

(define acct1 (make-account))
(define acct2 (make-account))
((acct1 'balance))               => 0
((acct1 'deposit) 100)           => 100
((acct1 'withdraw) 30)           => 70
((acct1 'withdraw) 200)          => "Insufficient funds"

;; acct2 is a different account from acct1!
((acct2 'balance))               => 0