Scheme Programming/A taste of Scheme

Scheme Programming
 ← Using a Scheme interpreter A taste of Scheme Scheme Datatypes → 

Now that we have an idea of how to run a Scheme interpreter, let's see what we can do with it in some short examples. We won't consider these programs in any depth. For now, the intention is to give the reader a feel for Scheme.

A Scheme interpreter evaluates Scheme expressions typed by the user. With our interpreter running, we first evaluate a very simple expression.

> 4
4

When we enter '4' and hit Enter, we are asking our Scheme interpreter to evaluate the expression '4'. Unsurprisingly, 4 evaluates to 4.

Next, we try some basic arithmetic.

> (+ 3 6)
9
> (* 18 5)  ; multiplication
90
> (- 4 7)
-3

These examples illustrate some important things about Scheme syntax. First, the syntax is fully-parenthesized; the parentheses in the above expressions are required, and extra parentheses cannot be added without changing the meaning of the expression. For example, the following causes the interpreter to report an error:

> ((+ 3 6))
;; Error: application of non-procedure 9

Secondly, we see that Scheme uses prefix notation. In each expression, the operator (+, *, -) comes before the operands (the numbers). This is unlike the usual ("infix") convention of mathematical notation, in which the operator is written between the operands. For example, the following isn't a valid Scheme expression:

> (8 * 5 - 3)
;; Error: application of non-procedure 5

It's easy to translate it into Scheme, however:

> (- (* 8 5) 3)
37

Notice that here we've used the expression (* 8 5) as an operand of another expression! This is valid Scheme, and we will see such nested expressions everywhere.

Scheme allows us to name values for use in other expressions:

> (define x 20)
> x
20
> (define y (+ x 4))
> (* y 2)
48

The definition (define x 20) causes the interpreter to define x to mean the value 20. When x appears in code following the definition, it will be replaced by this value. We can then use x in the definition of y.

We can also define procedures. Like the built-in operators +, -, etc., a procedure takes a certain number of parameters (also known as arguments) and computes a value.

> (define (square x)
    (* x x))
> (square 5)
25

Here, we have defined square to mean a procedure taking one parameter, x, and returning the value of (* x x). The form of this definition is a little different from those we saw earlier; here, the name being defined appears in parentheses, followed by the names of the procedure's parameters. To use this newly-defined procedure, we evaluate (square 5).

Here's a more complex procedure which calculates the area of a triangle with sides , , and using Heron's formula:

> (define (heron a b c)
    (let ((s (/ (+ a b c) 2)))
      (sqrt (* s (- s a) (- s b) (- s c)))))
> (heron 4 13 15)
24

In this procedure, we need to use the value , defined by

,

four times. The definition of heron would be tedious to write and hard to read if we were to use (/ (+ a b c) 2) [1] for each occurrence of . We solve this problem using the let form, which allows us to temporarily associate the name s with this value.[2]

We can compare values, or test them for certain properties.

> (= 4 (+ 3 1))
#t
> (< 9 7)
#f
> (positive? 5)
#t
> (even? 5)
#f

What are the #t and #f values which Scheme gives us back here? As you may have guessed, these are boolean values representing "true" and "false", respectively.

Predicates like positive? are procedures which take one argument and return a boolean value. It's a Scheme convention to give predicates names ending with '?'; hence we have even?, negative?, and many others.

Procedures can be recursive. Here's a classic example.

> (define (factorial n)
    (if (= n 0)
        1
        (* n (factorial (- n 1)))))
> (factorial 5)
120

Evaluating (factorial 5) gives us the value of , that is, , which we can express recursively as . More generally, we say that factorial of is 1 if is zero, and times the factorial of otherwise. This is precisely what the definition of factorial states in the language of Scheme:

(define (factorial n)
  (if (= n 0)                      ; is the argument 0?
      1                            ; then the answer is 1
      (* n (factorial (- n 1)))))  ; otherwise, it's n * (n - 1)!

Recursion is a critical notion in Scheme (and in computer science in general) that we will discuss in greater depth in later sections.

Another new thing in the definition of factorial is the use of the if form, which evaluates its first argument, then either its second or third argument depending on whether the first was true or false. If the first argument evaluates to true, then we get the second argument of the if form; otherwise, we get the third. [3]

> (if #t 1 0)
1
> (if (> 2 3) 1 0)
0
Exercises (Solutions)
  1. Why does evaluating ((+ 3 6)) cause Scheme to report the "application of a non-procedure"?
  1. In previous examples we gave + two arguments. Here, though, we've given it three. Is this correct? In general, Scheme will report an error if we give a procedure too few or too many arguments. +, though, is a variadic procedure, which means that it can be passed any number of arguments. The expression (+ a b c) is equivalent to (+ (+ a b) c). -, *, and / are also variadic.
  2. If you're familiar with programming languages with "local variables", you can think of names bound by let as much the same thing.
  3. For now, we'll assume that saying something is "true or false" means it's a boolean--that is, either #t or #f. In practice, every Scheme object has a boolean value; namely, #f is false and everything else is true.