CSE 341 - Programming Languages - Autumn 2006
Scheme

Side Effects in Scheme

When To Use Side Effects in Scheme?

Assignment and List Surgery

Scheme includes a special form for assignment:
 (set! var expr) 
Example:
(define x 10)
(write x)
(set! x (+ x 1))
(write x)

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

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.

Example:

(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 the variables incr and get, and just give them null as 
;; a value for now (it will be reset later)
(define incr ())
(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:

(get)

(incr 10)
(get)

(incr 100)
(get)
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.

This example is also available as a separate web page bankaccount.scm.

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

      ;; return the current balance
      (define (balance)
         my-balance)

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

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

     ;; 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))))

      dispatch))
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