
For these first few lectures, we will draw heavily on an on-line textbook: An Introduction to Scheme and its Implementation. The reading for this lecture is the Overview and the first two parts of the introduction: "What is Scheme?" and "Basic Scheme Features".
You may want to check the online Scheme Frequently Asked Questions list if you have other questions about the language.
Scheme has a number of primitives. Here are some examples:
Most languages have some means of specifying primitive data values like these. Since you type them in as they are meant to be taken literally, these are called literals. Here's how you specify them in Scheme:
| numbers | 3, 5.2, etc. |
| characters | Use a "#\" preceding the character: #\a for the
character 'a', etc. |
| strings | Use quotation marks as in C or Java: "this is a string".
|
| symbols | Scheme and some other languages have the ability to reference the symbols
in their own code as a separate datatype. In Scheme, these are written
by putting a single quote in front of the symbol: 'a, 'hello,
etc. |
| booleans | #t and #f for true and false respectively |
Variables names (identifiers) in Scheme are a little more flexible than what
you're probably used to. Not only are letters, number, and underscores (_)
permissible, but so are most other characters: ?, !,+,-, etc. So, all of the
following are legal identifiers in Scheme: remove-first, zero?,
change!, this+that, +.
Scheme has a number of pre-defined functions, such as + (addition),
- (subtraction, * (multiplication), =
(numeric compare), eq? (general compare), display
(write to the screen), zero? (test for zero), etc. Be careful,
though: these don't parse any differently than other functions and can be
re-defined to be what you want them to be. In Scheme "+" is just
the name of the pre-defined addition function, not anything else special.
You can define your own functions, too, which we'll cover in a later lecture.
(<operator> <operand1> <operand2> <operand3> ....)What's this? A programming language with no semi-colons, commas, curly braces, etc? That's right. One of the things you'll learn very early in this class is that those are all just syntactic stuff that gets parsed out very early in the process. Languages use different syntaxes and may look very different on the outside, but inside they're really very similar—that's what this class is all about!
(define <name> <expression>)
The expression is evaluated and given the name.
Reading an expression also goes by the name parsing. Parsing translates the string that the user types into a data structure on which the evaluator can operate.
Some of the notation used to describe syntax and algorithms for doing simple parsing should have be covered in CS 236 and CS 252. We will not talk about how to do parsing in this course. More details on parsing are covered in the compiler course (CS 431).
==> 255 255Here we see an important behavior of Scheme. As I said before, Scheme evaluates everything presented to it. Primitive objects, such as numbers, evaluate to themselves. We can combine primitive objects with appropriate operators to form expressions. A compound expression is always enclosed in parentheses. This may be different than your previous experience with other programming languages where parentheses are used to denote optional grouping. You cannot insert and delete parentheses from a Scheme expression without changing the meaning of the expression.
Here are some examples of Scheme expressions and the results of their evaluation:
==> (* 2 2) 4 ==> (- 4 2) 2 ==> (+ 3 5.2) 8.2 ==> (/ 4 2) 2 ==> (/ 1 3) .333333333333} ==> (- -3 -5) 2There are several important points to make about the above Scheme session. First, note that the leftmost element in a compound expression is the operator, followed by the operands. The Scheme evaluator determines the value of the expression by applying the procedure specified by the operator to the value of the operands. That last sentence was extremely important, so make sure you understand what it says.
Another point to make is that Scheme does not differentiate between integers and real numbers. The result of adding 3 to 5.2 is 8.2. The result of dividing 1 by 3 is .33333. This will seem obvious to those of you without previous programming experience, but other computer languages frequently make this distinction.
Lastly, notice that in the last expression, the "-" occurs three
times and means two different things. When it's the leftmost element in the
expression, it represents the operation that is to take place, when its appended
to the front of a number, it means that the number is negative. Spacing is important
here; (- - 3 -5) will produce an error.
When the operator proceeds the operands, we say that the expression is using prefix notation. Scheme uses prefix notation exclusively. Prefix notation has several advantages over other notations:
(+ (/ 4 2) (+ 4 6))building a complex expression out of two simpler ones. Of course, this nesting can go on for any number of levels. We'll see later how Scheme deals with nested expressions. One problem with prefix notation is that sometimes there are so many parentheses that we get lost:
(* (* (+ 3 5) (- 3 (/ 4 3))) (- (* (+ 4 5) (+ 7 6)) 4))If we really try, we could probably figure this out, but it is not clear what this means to the casual observer. In order to help with this kind of confusion, most Scheme programmers adopt some sort of indentation scheme to allow them to read programs easily. If we write the above expression over a number of lines and indent them carefully, we can more easily see how it should be evaluated:
(* (* (+ 3 5) (- 3 (/ 4 3))) (- (* (+ 4 5) (+ 7 6)) 4))Of course, this isn't the only way to indent this and you may prefer another, but I think anyone will agree that this is better than the original. It doesn't matter what kind of indenting you use in this course as long as your meaning can be clearly understood.
Consider for a moment how one talks about problems in daily life. To find the circumference of a circle with a radius of 3, we say multiply 3 times 2 times pi. Pi is the name of a number. Its a name that means something to anyone who has had seventh grade mathematics and furthermore gives us a convenient handle for referring to a not so convenient number.
To associate a name with a number in Scheme, we use a special operator called define. When you type
==> (define PI 3.14159) pi
you cause the Scheme interpreter to associate the name pi with the computational object 3.14159. Just like all other expressions, this Scheme expression returns a value. In this case, the name that is being defined.
Now that we have associated a name with the object 3.14159, we can refer to that object by name. For instance:
==> pi 3.14159 ==> (+ 2 pi) 5.14159and so on. Here are some more examples:
==> (define radius 6) radius ==> (* 2 pi radius) 37.70 ==> (define circumference (* 2 pi radius)) circumference} ==> circumference 37.70
Notice that after we have defined circumference we can ask Scheme to
evaluate it. Its important to realize that the value of circumference
is 37.70 not (* 2 pi radius). The expression is evaluated
at the time of the definition and its value is given the name circumference.
Consider the following sequence of three definitions:
(define a 3)
(define b (+ a 1))
(define a 4)
What are the values of a and b after each definition? What is the value of b at
the end? Try it in your interpreter to see for yourself.
As I said before, define is Scheme's simplest
means of abstraction. Being able to name computational objects allows names
to be associated with complex results, such as circumference. In
addition, we don't need to repeat the whole string and, more importantly,
reevaluate it each time we need to know the circumference. Finally, we
can build programs incrementally by successive definition. This last point
is very important. Programming in Scheme is done by defining one thing,
and then the next, building up a program in a series of steps. At each
point along the way the objects that have been defined are available for
use and testing.
We'll talk a lot more about functions in Lecture 4, but here's a simple syntax for defining functions:
(define (inc x)
(+ x 1))
Notice that it has the following parts:
(function-name function-parameters)
. You can have as many parameters as you want for the function.Go ahead and use this syntax for declaring function for now, but be aware that it's really short-hand for a much more powerful mechanism for defining functions, which we'll cover in Lecture 4.
Yes, expressions, not statements!
All high-level languages have a way of expressing the idea of choice in an algorithm, although some do not have conditional expressions or statements. In Scheme, there are two different forms of conditional expressions. The first form is the most general and is called cond. Cond is used like this:
(define (zero? x) (cond ((= x 0) true) ((< x 0) false) ((> x 0) false))))This function can be used like this:
==>(zero? 12) #f ==>(zero? 0) #t ==>(zero? -5) #fThe general form of the cond statement in Scheme is
(cond (<p1> <e1>) (<p2> <e2>) . . . (<pn> <en>))
Each of the arguments to the cond statement, called clauses, is a pair of expressions. The first one, the predicate, is a boolean expression. The second part of the clause, called the consequent, is an expression to evaluate if the predicate is true. When the Scheme interpreter comes across a cond statement, it finds the value of the first predicate. If it's value is false, the next predicate is evaluated. This continues until a predicate that evaluates to true is found, then the consequent expression associated with that predicate is evaluated and its value returned as the value of the cond statement. If none of the predicates evaluate to true, cond returns a value of false.
Its easy to make mistakes with the parentheses in a cond statement, but if you remember a few points, its not that hard. Think of cond as an operator with a number of arguments, each of the arguments is enclosed in parentheses. The predicate and consequent expressions are just like the expressions that we've seen so far. They can be simple expressions or compound expressions.
One special form of the cond statement involves the use of else. We had three clauses in our zero? function, but only wanted two different values returned. What we really want to say is ``if the argument to the function is 0, then return 1, otherwise return 0''. We can express this like so:
(define (zero? x) (cond ((= x 0) true) (else false))))The else is a predicate that always evaluates to >true, thus if none of the predicates before it are true, the consequent associated with the else clause is always evaluated. Note that it is syntactically incorrect to enclose the else in parentheses.
Another conditional statement that can be used much like that cond is called if. Using the if statement, we could rewrite the zero? predicate like this:
(define (zero? x) (if (= x 0) true false)))
The if operator takes three arguments, a predicate, a consequent, and an alternative. The predicate is evaluated, if it is true the consequent is evaluated and its value returned, if the predicate's value is false, the alternative is evaluated.
We can combine predicates to form complex predicates using the boolean operators and, or, and not. The boolean operators work just like arithmetic operators, using prefix notation. You must take care that the predicate is something that can be determined algorithmically. In other words, we have to tell the computer how to determine the boolean value of something. This shouldn't surprise you since computer science is concerned with ``how to'', not ``what is''. For example, in English we may say ``if we owe the customer change, ...''. In a computer language, we must tell the computer how to determine if there is any change needed. We might write
(if (> (- amountTendered cost) 0) <consequent> <alternative>)
to accomplish this algorithmically. Here's another example using the cond statement. This function determines the sign of its argument and returns -1, 0, or 1 respectively. Note that this is not a predicate (and hence no ?) since it doesn't return a simple yes or no answer.
==> (define (sign x) (cond ((< x 0) -1) ((> x 0) 1) (else 0)))) SIGN ==> (sign -2) -1 ==> (sign 4) 1 ==> (sign 0) 0
Like C and Java, Scheme uses both single-line and block comments.
Single line comments are specified using a semicolon:
(define pi 3.14) ; OK, it's sloppy a sloppy approximation
Block comments are specified using #| and |#:
#|
Here is my code for homework #1
by Jean Nyess
|#

Last updated at 9:39 pm on Friday, September 17, 2004.