Printable manual:
brl.pdf (340Kb) or
brl.dvi (204Kb)

3. Introducing Scheme

It's fairly easy to pick up basic BRL syntax just by looking at examples. However, learning some Scheme beforehand can clarify the subtleties of examples in this manual and help you go beyond the examples on your own.

The Scheme programming language was designed with teaching in mind. There is a very friendly tool available for learning it called DrScheme.

Alternatively, if you're using the machine you installed BRL on, you can start Kawa Scheme from the shell or command prompt by typing this:

java kawa.repl

Kawa Scheme will let you try Scheme/Java integration, but won't provide nearly as friendly an environment as DrScheme.

3.1 Simple Expressions

We'll begin, as is customary in introductions to programming languages, with the syntax for printing the words "Hello, World" on the screen:

> "Hello, World"
Hello, World

The > sign represents the prompt at which you type. It might be #|kawa:1|# or something else depending on your Scheme implementation. But the result is the same. You type "Hello, World" (including the quotes) and you get "Hello, World". This may be surprising if you're used to other computer languages in which "Hello World" can only be used as an argument to a command. Scheme does not have this command/argument dichotomy. You use the term expression for both concepts. Everything you type in a Scheme interpreter is an expression. "Hello, World" is a what's called a self-evaluating expression, or a constant.

Here's how you assign expressions to variables:

> (define h "Hello, World")
> h
Hello, World

You may have used other languages in which variables have a special character to mark them, e.g. $h or @h. In Scheme, variable names are simple. A variable name is an expression that evaluates to whatever you assigned to it. The define expression above is an example of variable assignment, more commonly called binding in Scheme. The variable name is said to be bound to a value.

> (string-append h "!!")
Hello, World!!

This illustrates the most common way to combine expressions. The expression "!!" is a constant string, much like the "Hello, world" you typed earlier. To the left of that is h, the variable we just bound to a string value. Then there's string-append, a function that concatenates strings.

An important thing to note here is that string-append is just another variable, like h. The only difference is that h is bound to a string value, while string-append is bound to a procedure value. Procedures are just another Scheme data type, like strings or numbers.

This makes the process of evaluating (string-append h "!!") simple and uniform. Each expression within the parentheses is evaluated, then the value of the first expression (in this case string-append) is applied, i.e. invoked with the remaining values as function arguments.

Caution: If you're accustomed to other languages in which adding extra parentheses does not change the meaning, you'll need to unlearn that habit with Scheme. For example h is just the variable h, but (h) means to treat h as a function with no arguments and apply it.

Let's review how this expression is evaluated:

(string-append h "!!")

First, each sub-expression within the parens is evaluated.

  1. "!!" is self-evaluating; its value is "!!".
  2. h is a variable; the value bound to it is "Hello, World".
  3. string-append is a variable; the value bound to it is a procedure.

This makes the expression equivalent to

(string-append "Hello, World" "!!")

Next, the procedure bound to string-append is applied to the arguments "Hello, World" and "!!", returning the value "Hello, World!!".

As you can see, this is a very simple process, called procedure application. Don't be intimidated by the use of terms like procedure, arguments, variable, bound, and self-evaluating expression to describe it. I only introduce those terms here to help you read Scheme documentation later.

Not every Scheme expression works by procedure application. You might have guessed this, as the (define h "Hello, World") expression could not have worked by first evaluating h and passing its value as an argument to define. At that point, h was not defined yet and could not evaluate to anything.

There are other examples in Scheme of expressions that are not procedure application, but in general the syntax is the same: First an open parenthesis, then what you're doing, then what you're doing it to, then a close parenthesis. Although simple and uniform, this syntax can be a little confusing at first for math:

(+ (* 2 3 4) 5 6)

The normal math notation would be 2x3x4 + 5 + 6. You will find it less confusing if you read + in Scheme code as "the sum of" and * as "the product of."

3.2 Defining Procedures

One of Scheme's great strengths is how simple it is to extend with new functions. Here's a simple example:

> (define (greet name) (string-append "Hello, " name "!!"))
> (greet "Bruce")
Hello, Bruce!!

Note that there are two parentheses at the end of the definition. One closes the string-append expression, while the other closes define. Scheme code is usually written in an editor such that when you type a close paren, the corresponding open paren is highlighted. Also, since it is parens and not line breaks that end a Scheme expression, definitions like the one above are often split into multiple lines with indentation clarifying how "deep" the code is within parentheses.

Your procedure can be used anywhere you would normally put a Scheme expression:

> (string-append "Wow!  " (greet "Chris") "  Long time, eh?")
Wow!  Hello, Chris!!  Long time, eh?

You can use your function to define other functions:

> (define (safe-greet name)
    (if (string? name)
        (greet name)
        (greet "NON-STRING!")))
> (safe-greet 2)

Also demonstrated in the above example is the if syntax. If the first argument (string? name) evaluates true(1), then the second argument (greet name) is evaluated. Otherwise the third argument (greet "NON-STRING!") is evaluated.

Here's a better example of if:

> (define (price n)
    (string-append "just "
                   (number->string n)
                   (if (= 1 n) "dollar" "dollars")))

This demonstrates that an if expression can go anywhere. This may seem obvious if you're new to programming, but there are many languages in which if is used more restrictively. People accustomed to such languages may be surprised to see an if expression used as an argument to a procedure. As an exercise, rewrite safe-greet so that greet appears only once in its definition. Do this by making the argument to greet be an if expression.

Note that number->string is just a regular function name. There is nothing special about the characters -> in the middle of a Scheme identifier.

3.3 Data Structures

This section will describe all but the least-used data structures in Scheme. To start, let's look at the simplest structure: a pair. This structure groups two items together. The items are retrieved with the car and cdr procedures:

> (define c (cons 1 2))
> (car c)
> (cdr c)

Try doing something similar with strings instead of numbers, but do it without looking at the above example. Memorize what cons, car and cdr do.

Did you do it? Good, we're done covering all but the least-used data structures in Scheme. But wait! We've only covered one very simple data structure. How can I say we've covered so much?

What makes pairs so powerful is that any data type can be stored in them, including other pairs. This allows one to string together (or, as the LISP community likes to say, cons up) a list by putting the first element of the list in the car and another pair in the cdr. This second pair has the second list element as its car and the rest of the list as its cdr, and so on. Eventually you reach the end of the list, where the cdr is a special marker known as the empty list.

> (define nums (list 1 2 3))
> (car nums)
> (cdr nums)
(2 3)

Scheme includes many tools for list processing. I will demonstrate two of them here.

> (define (square x) (* x x))
> (map square nums)
(1 4 9)
> (apply + (map square nums))

As you can see, map applies a procedure to each element of a list and returns a list of the results. In this example, square was applied to 1, 2 and 3, resulting in 1, 4 and 9, respectively. The apply procedure was used to call the + procedure with 1, 4 and 9 as arguments, returning 14.

The uses of map and apply are limitless. For example, if you had a procedure that computed sales tax, you could use map to get a list of sales taxes from a list of prices, and then use apply to total them. But the great thing about Scheme is not the existence of generally-useful procedures like map and apply, but how easily you can create such generally-useful procedures yourself.

For example, suppose you wanted to quiz yourself on arithmetic. Whether you're doing addition, multiplication, division or subtraction, the basic idea is the same. You take two numbers, perform an operation on them, and compare that to input. If the correct answer was input, say "Correct!", otherwise give the correct answer.

> (define (quiz op n1 n2)
    (display "Your answer? ")
    (if (equal? (op n1 n2) (read))
        (op n1 n2)))
> (quiz * 2 3)
Your answer? 6

In other languages you would have had to write separate pieces of your program for the various arithmetic operations. But with Scheme you've written one general-purpose procedure that works with all of them, plus operations that you didn't even know existed. Try it with remainder, lcm and gcd. Not bad for a five-line program, eh?

3.4 Learning More

It's only fair to warn you that there are few resources out there just for learning Scheme. There are plenty of books that might seem to be presenting Scheme, but they are actually teaching Computer Science, and expecting you to learn Scheme as you go along. Your best approach if you just want to use BRL is to take one of these books and skim through anything that seems difficult to understand, focusing on the examples.

Computer Science principles will be very useful to you if you tackle a large software project, but for the most common use of BRL, i.e. web pages, it's enough just to know the basics of Scheme.

Another good way to learn Scheme is to look at the specification, R5RS. This document is really targeted at people making programs that interpret or compile Scheme, not those who simply want to program in Scheme. It is rather dense in places, and some of the examples are tricky because they demonstrate several principles at once. However, you can still find it a useful introduction if you don't let yourself get bogged down.

A third option is to simply keep reading this manual and learn from example, then go to R5RS or a textbook later.

This document was generated by Bruce R. Lewis on 2003-6-9 using texi2html

GNU   SourceForge