Conditional expresssions and recursion

References

Related links

  1. Demorunner applet
  2. Demorunner servlet

Mini-scheme interpreter

You need a Java-enabled browser to run the WebSILK applet

The conditional statement (if A B C)

Today we discuss the "if" statement which has the syntax
   (if TEST THEN ELSE)
where TEST is an expression which returns a true or false value (like a test (< x 1) or (equal? A "Hello")). For example,
  >> (if (< 1 2) 7 12)
     7

  >> (if (< 10 2) 7 12)
     12

  >> (if (equal? "hi" "Hi") "apple" "banana")
     banana

  >> (if (equal? "hi" "hi") "apple" "banana")
     apple

  >> (if (equal? "hi" 'hi) "apple" "banana")
     banana

Using the choice component

One good use of the if statement is for adding actions to choice components. For example, here is a simple program which has an action associated to a choice component. When the user selects a greeting. The program writes the language of the greeting in a textfield.
(define win (window "choice demo"))

(define CHaction  (action (lambda (e)
   (if (equal? (readstring CH) "Hello") (writeexpr T "English"))
   (if (equal? (readstring CH) "Bon Jour") (writeexpr T "French"))
   (if (equal? (readstring CH) "Gutten Tag") (writeexpr T "German"))
   (if (equal? (readstring CH) "Konichi-wa") (writeexpr T "Japanese"))
   (if (equal? (readstring CH) "Hola") (writeexpr T "Spanish"))
)))

(define CH (choice "Hello" "Bon Jour" "Gutten Tag" "Konichi-wa" "Hola" CHaction))
(define T (textfield "" 20))

(.add win
  (col
    (label "Choice demo")
    CH
    T))

(.pack win)
(.show win)

The program needs to use readString because (read CH) will try to read the string as a scheme term. So for Bon Jour, it will just read the Bon, and for Gutten Tag it will just read the Gutten.


Recursion

Next we trace through a simple, but very important, program which uses a procedure f whose definition contains another call to f, this is called textual recursion.
  (define win (window "Recursion Demo" 
                       (CourierBold 40)))        ;; create a window
  (.resize win 200 200)                          ;; resize it
  (define T (textfield "" 10))                   ;; create a textfield
  (.add win T)                                   ;; add it to the window
  (.pack win)                                    ;; pack the window
  (.show win)                                    ;; make it appear on the screen

  (define (f x)                                  ;; add the definition of f to the environment
    (Thread.sleep 1000L)
    (writeexpr T x)
    (if (< x 1) "done" (f (- x 1))))

  (f 3)                                         ;; start evolving the expression (f 3)

Lets now trace through evolution of this program (f 3)
==> (define win (window "Recursion Demo" (CourierBold 40)))  create a window

==> (.resize win 200 200)                           resize it

==> (define T (textfield "" 10))                    create a textfield

==> (.add win T)                                    add it to the window

==> (.pack win)                                     pack the window

==> (.show win)                                     make it appear on the screen

==> (define (f x) (Thread.sleep 1000L) (writeexpr T x) (if (< x 1) "done" (f (- x 1))))
                                                    define the procedure f

==> (f 3)                                           start evaluating (f 3)

==> (Thread.sleep 1000L) (writeexpr T 3) (if (< 3 1) "done" (f (- 3 1)))   here it sleeps for 1 second

==>                      (writeexpr T 3) (if (< 3 1) "done" (f (- 3 1)))   here it writes 3 on the textfield T

==>                                      (if (< 3 1) "done" (f (- 3 1)))

==>                                      (if      #f "done" (f (- 3 1))

==>                                                         (f (- 3 1))

==> (f 2)

==> (Thread.sleep 1000L) (writeexpr T 2) (if (< 2 1) "done" (f (- 2 1)))   sleep for a second

==>                      (writeexpr T 2) (if (< 2 1) "done" (f (- 2 1)))   write 2 on textfield T

==>                                      (if (< 2 1) "done" (f (- 2 1)))

==>                                      (if      #f "done" (f (- 2 1))

==>                                                         (f (- 2 1))

==> (f 1)

==> (Thread.sleep 1000L) (writeexpr T 1) (if (< 1 1) "done" (f (- 1 1)))   sleep for 1 second

==>                      (writeexpr T 1) (if (< 1 1) "done" (f (- 1 1)))   write 1 on textfield T

==>                                      (if (< 1 1) "done" (f (- 1 1)))

==>                                      (if      #f "done" (f (- 1 1))

==>                                                         (f (- 1 1))

==> (f 0)

==> (Thread.sleep 1000L) (writeexpr T 0) (if (< 0 1) "done" (f (- 0 1)))  sleep for 1 second

==>                      (writeexpr T 0) (if (< 0 1) "done" (f (- 0 1)))  write 0 on textfield T

==>                                      (if (< 0 1) "done" (f (- 0 1)))

==>                                      (if      #t "done" (f (- 0 1))

==> "done"


The Substitution Model

Up to this point we have primarily focussed our attention on "Reactive Programs" in which the computer is programmed to respond to user input by carrying out a few commands specified by the author of the program. For these programs, the computer responds to user inputs almost immediately (after the system has been completely downloaded into the browser). Another class of programs is the recursive programs. These programs differ from reactive programs in that the procedures are allowed to call themselves. This can sometimes lead to infinite loops if the programmer is not careful, but it also enables computers to solve much more complex problems and, some believe, possibly even to faithfully imitate human intelligence itself. To understand this class of programs we need a new model of computation in which an expression E is evaluated by successively transforming it using the Scheme evaluation rules into new expressions, until we reach a final expression which can not be evaluate any further (e.g., a number or a string, or a list). This model is called the Substition model because user-defined procedure calls are handled using substition as described below.

The Substition Rule

This additional power comes from a very simple rule, called the Substitution rule, for evaluating Scheme programs. The rule states that you evaluate a procedure call
    (F a1 a2 ... an)
to a user defined procedure F with a definition
    (define (F x1 ... xn) 
            BODY)
by replacing the call with the BODY, but where the variables x1, ..., xn have been replaced with the arguments a1,...,an to the call. Some examples will make this clear.


Countdown: the simplest recursive procedure

The simplest recursive procedure is the countdown procedure which simply counts from its argument N down to zero.
  (define (countdown n)
    (if (= n 0) 'done (countdown (- n 1))))
Tracing through the evaluation of the expression (countdown 3) we see that the call (countdown 3) gets transformed into the calls (countdown 2), (countdown 1), and finally (countdown 0), which is transformed to the quoted symbol 'done, and the evaluation stops as quoted symbols can not be further evaluated.
  (countdown 3)
    (if (= 3 0) 'done (countdown (- 3 1)))
    (if false   'done (countdown (- 3 1)))
    (countdown (- 3 1))
  (countdown 2)
    (if (= 2 0) 'done (countdown (- 2 1)))
    (if false   'done (countdown (- 2 1)))
    (countdown (- 2 1))
  (countdown 1)
    (if (= 1 0) 'done (countdown (- 1 1)))
    (if false   'done (countdown (- 1 1)))
    (countdown (- 1 1))
  (countdown 0)
    (if (= 0 0) 'done (countdown (- 0 1)))
  'done


Harnessing Countdown

The countdown procedure can be harnassed to perform some useful work by adding additional expressions before the "if". These will be evaluated each time through the loop. Thus, to create a "timer applet" which shows the the seconds countdown in a textfield T, we could use the following variant of the countdown procedure:
  (define T (textfield "" 20 (HelveticaBold 24)))
  (define win (window "countdown" (col T)))
  (.pack win) (.show win)

  (define (sleep N) (Thread.sleep (.longValue (Math.round (* 1000.0 N)))))

  (define (visualcountdown n)
    (writeexpr T n)
    (sleep 0.33)
    (if (= n 0) 'done (visualcountdown (- n 1))))
  (visualcountdown 10)
You should try running this applet in the Scheme interpreter below: When we trace this new procedure the body contains three items which are grouped together and we must evaluate them from left to right, returning the result of the last expression. We have indicated this by enclosing the body in an expression of the form
    (begin E1 E2 ... En)
Using this convention we can now trace the visual counter program.
  (visualcounter 3)
    (begin (writeexpr T 3) (sleep 1000) (if (= 3 0) 'done (visualcounter (- 3 1))))
    (begin                 (sleep 1000) (if (= 3 0) 'done (visualcounter (- 3 1))))
    (begin                              (if (= 3 0) 'done (visualcounter (- 3 1))))
                                        (if (= 3 0) 'done (visualcounter (- 3 1)))
                                        (if false   'done (visualcounter (- 3 1)))
                                                          (visualcounter (- 3 1))
  (visualcounter 2)
    (begin (writeexpr T 2) (sleep 1000) (if (= 2 0) 'done (visualcounter (- 2 1))))
    (begin                 (sleep 1000) (if (= 2 0) 'done (visualcounter (- 2 1))))
    (begin                              (if (= 2 0) 'done (visualcounter (- 2 1))))
                                        (if (= 2 0) 'done (visualcounter (- 2 1)))
                                        (if false   'done (visualcounter (- 2 1))))
                                                          (visualcounter (- 2 1))
  (visualcounter 1)
    (begin (writeexpr T 1) (sleep 1000) (if (= 1 0) 'done (visualcounter (- 1 1))))
    (begin                 (sleep 1000) (if (= 1 0) 'done (visualcounter (- 1 1))))
    (begin                              (if (= 1 0) 'done (visualcounter (- 1 1))))
                                        (if (= 1 0) 'done (visualcounter (- 1 1)))
                                        (if false   'done (visualcounter (- 1 1)))
                                                          (visualcounter (- 1 1))
  (visualcounter 0)
    (begin (writeexpr T 0) (sleep 1000) (if (= 0 0) 'done (visualcounter (- 0 1))))
    (begin                 (sleep 1000) (if (= 0 0) 'done (visualcounter (- 0 1))))
    (begin                              (if (= 0 0) 'done (visualcounter (- 0 1))))
                                        (if (= 0 0) 'done (visualcounter (- 0 1)))

  'done
The counter procedure can also be modified to create a turtle that takes N random steps on a graphical canvase.


Summing a sequence of numbers using a modified Countdown

A more interesting application is to find the sum of the integers from N down to 1. This can be done with following program:


  (define TA (textarea 10 20 (HelveticaBold 24)))
  (define win (window "countdown" (col TA)))
  (.pack win) (.show win)
  (define (sleep N) (Thread.sleep (.longValue (Math.round (* 1000.0 N)))))

  (define (sumton n s)
    (writeexpr TA (string-append (readstring TA) "\n" (list 'sumton n s)))
    (sleep 1.0)
    (if (= n 0) 'done (sumton (- n 1) (+ s n))))
  (sumton 10 0)
Each time through the countdown loop, this procedure does the following: The textfield T is a new parameter of this procedure so we can call sumton on different textfields.

Since the intermediate results are shown in the textfield the user can watch the sum accumulate.


Summing a sequence of numbers behind the scenes

If we only wanted the final result and were not interested in seeing the intermediate results, we could do away with the textfield and store the intermediate value as a parameter of the procedure, which we have renamed p.
  (define (p n Subtotal)
    (if (= n 0) 
        Subtotal 
        (p (- n 1) (+ n Subtotal))))

  ;; make the GUI
  (define X (textfield "100" 10))
  (define T (label "0" 10))
  (define B (button "sum numbers from 1 to n" 
        (action (lambda(e)
           (writeexpr T "")
           (writeexpr T 
               (p (readexpr X) 0))))))

  (define win (window "test" (HelveticaPlain 18)
    (tablebyrow 2 (label "n") X 
                B (label "")
                (label "sum:") T)))

  (.pack win) (.show win)

It is interesting to trace through this new procedure for a simple call:
  (p 5 0)
    (if (= 5 0)  0 (p (- 5 1) (+ 5 0)))
    (if false    0 (p (- 5 1) (+ 5 0)))
                   (p (- 5 1) (+ 5 0))
  (p 4 5)
    (if (= 4 0)  5 (p (- 4 1) (+ 4 5)))
    (if false    5 (p (- 4 1) (+ 4 5)))
                   (p (- 4 1) (+ 4 5))
  (p 3 9)
    (if (= 3 0)  9 (p (- 3 1) (+ 3 9)))
    (if false    9 (p (- 3 1) (+ 3 9)))
                   (p (- 3 1) (+ 3 9))
  (p 2 12)
    (if (= 2 0) 12 (p (- 2 1) (+ 2 12)))
    (if false   12 (p (- 2 1) (+ 2 12)))
                  (p (- 2 1) (+ 2 12))
  (p 1 14)
    (if (= 1 0) 14 (p (- 1 1) (+ 1 14)))
    (if false   14 (p (- 1 1) (+ 1 14)))
                  (p (- 1 1) (+ 1 14))
  (p 0 15)
    (if (= 0 0) 15 (p (- 0 1) (+ 0 15)))
    (if true    15 (p (- 0 1) (+ 0 15)))

  15


Tracing a procedure call at a higher level

The pattern we see is that (p a b) is transformed into (p (- a 1) (+ a b)) provided a is not equal to zero. When a=0, then it is transformed into b, which is the final result. Thus, we can trace through the call (p 10 0) by doing the indented "if" evaluation steps in our head, and we get:
  (p 10   0)
  (p  9  10)
  (p  8  19)
  (p  7  27)
  (p  6  34)
  (p  5  40)
  (p  4  45)
  (p  3  49)
  (p  2  52)
  (p  1  54)
  (p  0  55)
  55
and indeed, 55 = 10+9+8+7+6+5+4+3+2+1. Thus a call of
  (writeexpr T (p (readexpr X) 0))
would sum the numbers from 1 to whatever is in X and write the answer on T.


Simple Exponentiation

Next we consider a slightly more complicated example, raising a number to a power.
    (define (power x n)
      (if (= n 0) 1 (* x (power x (- n 1)))))
As we can see in the trace below (where we have performed the evaluation of the "if" expression in our heads"), each successive call creates a larger expression to evaluate until n = 0. After this point, we have a large expression which is evaluated from the outside in. It is not hard to see that this method will require about 2n steps to raise a number x to the power n.
  (power 2 6)
  (* 2 (power 2 5))
  (* 2 (* 2 (power 2 4)))
  (* 2 (* 2 (* 2 (power 2 3))))
  (* 2 (* 2 (* 2 (* 2 (power 2 2)))))
  (* 2 (* 2 (* 2 (* 2 (* 2 (power 2 1))))))
  (* 2 (* 2 (* 2 (* 2 (* 2 (* 2 (power 2 0)))))))

  (* 2 (* 2 (* 2 (* 2 (* 2 (* 2     1      ))))))
  (* 2 (* 2 (* 2 (* 2 (* 2          2       )))))
  (* 2 (* 2 (* 2 (* 2               4        ))))
  (* 2 (* 2 (* 2                    8         )))
  (* 2 (* 2                        16          ))
  (* 2                             32           )
                                   64


Clever Exponentiation

Next we show a more clever implementation of exponentiation which is based on the following two rules of exponents:
  xN = (xN/2) 2         (N even)
  xN = x* (x(N-1)/2)2   (N odd)
The code for this procedure is as follows:
  (define (sq x) (* x x))

  (define (expt x n)
    (if (= n 0)
        1.0
       (if (even? n)
                (sq (expt x (/    n    2)))
           (* x (sq (expt x (/ (- n 1) 2)))))))
Tracing through this procedure for the case of raising 2 to the 6th we don't see much advantage. Indeed, it seems significantly more complex.
  (expt 2 6)
  (sq (expt 2 3))
  (sq (* 2 (sq (expt 2 1))))
  (sq (* 2 (sq (* 2 (expt 2 0)))))

  (sq (* 2 (sq (* 2 1.0))))
  (sq (* 2 (sq 2.0)))
  (sq (* 2 4.0))
  (sq 8.0)
  64.0
But the benefit of this approach comes when we try to raise 2 to the 33th power:
  (expt 2 33)
  (* 2 (expt 2 32))
  (* 2 (sq (expt 2 16)))
  (* 2 (sq (sq (expt 2 8))))
  (* 2 (sq (sq (sq (expt 2 4)))))
  (* 2 (sq (sq (sq (sq (expt 2 2))))))
  (* 2 (sq (sq (sq (sq (sq (expt 2 1)))))))
  (* 2 (sq (sq (sq (sq (sq (* 2 (expt 2 0))))))))

  (* 2 (sq (sq (sq (sq (sq (* 2 1.0)))))))
  (* 2 (sq (sq (sq (sq (sq 2.0))))))
  (* 2 (sq (sq (sq (sq 4.0)))))
  (* 2 (sq (sq (sq 16.0))))
  (* 2 (sq (sq 256.0)))
  (* 2 (sq 65536.0))
  (* 2 4294967296.0)
  8589934592.0
This procedure accomplishes in 14 steps what would require 66 steps for the power procedure. Observe that for each successive call of the expt procedure the argument is halved. Indeed, the number of steps required by the (expt x n) procedure is exactly twice the length of the number n when written as a binary number.







More Advanced Topics

Below we include some more advanced topics for those who want to explore these concepts a little further.


Clever Exponentiation and the RSA public key algorithm

This clever exponentiation idea is the most important procedure in the RSA public key algorithm. If you recall, the way to encode a secret message S with a key (E M) is to translate S to a number N (or maybe a sequence of numbers N1, N2, ... if S is a long message), and then to compute the encoded message by raising N to the Eth power, dividing by M, and taking the remainder. In Scheme, (modulo A M) is the function that takes the remainder of A divided by M, thus the RSA encoding of S is a number T computed by
  T =  (modulo NE M)
Similarly, we decode T to get back our original message N by raising it the E' power and taking the remainder upon division by M:
  N =  (modulo TE' M)
where (E',M) is the private key.

The point here is that M and E are typically very large numbers, possibly hundreds of decimal digits long, and the simple power procedure would run for billions of years without finishing. However, the clever exponentiation algorithm can be modified to compute (modulo NE M) in at most 2K steps where K is the lenght of N as a binary number, typically around 1000. The modified version is called exptmod and is written below:

  (import "java.math.BigInteger")    ;; need to use BigInteger's
  (define (exptmoddemo x n m)
    (define (sq x) (.multiply x x))
    (define zero (BigInteger. "0"))
    (define one (BigInteger. "1"))
    (define two (BigInteger. "2"))
    (define (even? n) (= (.mod n two) 0))

    (define (exptmod x n m)
      (.mod
        (if (.equals n zero)
          one
          (if (even? n)
                     (sq (exptmod x (.divide    n    two) m))
             (.multiply x (sq (exptmod x (.divide (.subtract n one) two) m)))))
      m))
    (exptmod (BigInteger. x) (BigInteger. n) (BigInteger. m))
  )

  (exptmoddemo "12321" "1234567890123456789901234567" "9876543210987654321")
Here we use mult and div instead of * and /, because we need to use special procedures (available in Java) for computing with very large numbers. As a final example, we show how to compute the remainder of (3 raised to the 33 power) when divided by 100. (Note: to save space we have abbreviated modulo to m in the following trace.)
  (exptmod 3 17 100)
  (m (* 3 (sq (exptmod 3 8)))) 100)
  (m (* 3 (sq (m  (sq (exptmod 3 4))) 100))) 100)
  (m (* 3 (sq (m (sq (m (sq (exptmod 3 2)) 100)) 100))) 100)
  (m (* 3 (sq (m (sq (m (sq (m (sq (exptmod 3 1)) 100)) 100)) 100))) 100)
  (m (* 3 (sq (m (sq (m (sq (m (sq (m (* 3 (sq  (exptmod 3 0))) 100 )) 100)) 100)) 100))) 100)
  (m (* 3 (sq (m (sq (m (sq (m (sq  (m (* 3 (sq      1       )) 100 )) 100)) 100)) 100))) 100)
  (m (* 3 (sq (m (sq (m (sq  (m (sq                  3               ) 100)) 100)) 100))) 100)
  (m (* 3 (sq (m (sq  (m (sq                         9                     ) 100)) 100))) 100)
  (m (* 3 (sq (m  (sq                               81                           ) 100))) 100)
  (m (* 3 (sq  (m                                 6461                             100))) 100)
  (m  (* 3 (sq                                      61                                  ) 100)
   (m                                            11163                                    100)
                                                    63
The basic algorithm is to either square or square and multiply by 3 and then to take the remainder. This is iterate once for each binary digit of the exponent E.