History
-
dialect of LISP (List and Symbol Processing Language), one of the first high-level languages (ca. 1958 -- about the same time as FORTRAN)
-
roots in list processing, lambda calculus: emphasizes use of recursion (rather than iteration), binding rather than assignment, thus
-
Scheme is an example of a (mostly) functional language
-
widespread use in AI
-
Scheme is interpreted (at least as we will use it). Hybrid compilation and interpretation as well as full compilation are also available
Why study Scheme or LISP?
-
a rather different approach to programming from C, Java, Pascal, C#
-
has a minimal syntax
-
based on functions rather than statements
-
dynamically typed
-
statically scoped (Scheme) dynamically scoped (LISP)
-
automatic storage management
Resources
The course resources page has a number of Scheme-related items and
links for you. From the MIT Scheme home page you can download the
Scheme system for Windows or Linux. The Scheme Reference manual is
also linked -- the early conceptual sections could be quite helpful
for increasing your understanding of the language. The Scheme User
Manual will be helpful in figuring out how to interact with the scheme
system.
The Scheme Calisthentics sheet has exercises to help you develop
your understanding by figuring out the meaning of various Scheme
expressions. The Cheat Sheet lists handy functions and special forms
that you will need to become familiar with.
The newsgroup comp.lang.scheme newsgroup and its associated FAQ
(frequently asked questions) document may be of interest. Note, however,
that it is considered extremely bad form to ask a newsgroup
for help with your course assignments and I will not look kindly
on such behavior.
Many concepts, more details
There are many important concepts applicable to programming languages
in general that we will encounter in Scheme. Unfortunately, there are
many more details that are peculiar to Scheme. Your effort in building your
understanding of the concepts will help
you to understand the language, but it easy
to lose sight of the concepts for all the details.
Concepts that we will encounter:
- binding
- unbound vs unassigned
- special form vs function call
- evaluated vs unevaluated sub-expressions
- environments
- type: static vs dynamic
- scope: static vs dynamic
- the notion of a function as distinct from its name
- lists as a fundamental data structure
- three (!) notions of equality
- processing lists using recursive functions
- programs as data
Notation Used in Notes
Italics will denote non-terminals, bar, for which
something else will be substituted.
(foo bar)
Multiple allowed substitutions are indicated by ellipsis:
(foo bar ...)
Running Scheme under Windows
Use "MIT Scheme" from the startup menu. This will bring up the
interpreter.
Compute the sum of 4 and 5
> (+ 4 5)
9
To exit
(quit)
or
(exit)
In Unix
control-D
also works.
Lexical Matters
Identifiers
Literals
Examples
42 -99 1.23 4.56e2
Strings
"hello there!"
"I said \"hello there\" to you"
"A backslash is the \\ character"
Booleans
#t
#f
Lists - but we'll save those for later
Expressions
Syntactically, an expression in scheme is either a literal, an
identifier, or a parenthesized list of expressions separated by
whitespace. That's really all there is to the basic
syntax. Unfortunately, this description of the syntax provides little
clue as to the semantics.
Function Calls
An expression of the form:
(function operand ...)
Examples
> (+ 4 5)
9
> (+ (+ 4 5) 9)
18
To evaluate a function call, evaluate the operands, then call the function
on those operands.
Prefix Notation and Parentheses in Scheme
-
prefix notation only
-
parentheses delimit expression
(* 5 (+ 4 6))
Contrast with Parentheses in C
-
parentheses control precedence
5 * (4 + 6)
-
some infix notation
4 + 6
-
some prefix notation
add(4, 6)
Contrast with no parentheses in PostScript
Postfix notation
5 4 6 + *
or rewritten in prefix notation
* 5 + 4 6
So it is possible to write prefix notation without parentheses, but notice that there is
a problem when functions take an arbitrary number of arguments -- you need something to
bracket the arguments. PostScript takes the approach that brackets are used when necessary (e.g. '{' '}' for code arrays, begin/end for local scopes. Scheme takes the approach that
parentheses are always used.
Special forms
Expressions that aren't function calls are called special forms. Whereas function calls have a uniform, simple rule for how they are evaluated, each special form has its own rules.
Scheme variables and bindings
Create binding in current environment using the define special form:
(define identifier expression)
This has two effects: bind the identifier to a location in the
current environment if that is not already done (more about
environments to come) and initialize that location with the value of
the expression.
Simple example:
(define foobar 42)
Expression could be complicated:
(define foobar (+ 40 2))
(define foobad)
creates a bound but uninitialized variable.
Variable reference
Give name to dereference (get binding)
(define foobar 42)
> foobar
42
> barfoo
ERROR: unbound variable: barfoo
> foobad
ERROR: unassigned variable: foobad
Variables are dynamically typed
> (define foobar 42)
> (+ foobar 3)
45
> (define foobar "hello there")
> (+ foobar 3)
ERROR: the object "hello there" is not of the correct type
for integer-add
How can you tell that define is a special form and not a function?
Assignments
In functional programming style, assignments are seldom used.
> (define x 99)
> (set! x 0)
> x
0
> (set! y 0)
ERROR: unbound variable: y
; in expression: (... y 0)
; in top level environment.
Compare (define x ...) with (set! x ...): key to understanding is that
define creates a binding of name to location and initializes while
set! changes the value of an existing binding.
Note again the difference between unbound and unassigned.
Also note that if x is already defined, define will behave like set!.
Either form of the assignment is likely to be more confusing than helpful and should be
avoided for now.
If special form
Case special form
General form
(case key
((case-values ...) expression)
...
(else expression)
)
Examples
> (define x 6)
> (case x
((2 3 5 7) (+ x 3))
((1 4 6 8 9) (+ x 100)))
106
> (case (+ x 2)
((3 5 7) 42)
((1 4 6) 99)
(else 33))
33
Sequencing special form
General form
(begin expression ...)
Example of a begin expression:
> (define x 0)
> (begin (set! x 5)
(+ x 1))
6
Iteration
Scheme do expression is similar to a C for loop.
(do ((variable init step)
...)
(test test-expression ...)
body-expression ...)
Example
> (do ((i 1 (+ i 1)))
((> i 10))
(printf "%d " i))
1 2 3 4 5 6 7 8 9 10
equivalent C code
for (i = 1; i <= 10; i++)
printf ("%d ", i);
Iteration Continued
step part may be omitted
(variable init)
same as
(variable init variable)
Example
> (do ((i 10 (- i 1))
(sum 0))
((= i 0) sum)
(set! sum (+ sum i)))
55
equivalent to C loop:
for (i = 10, sum = 0; i != 0; i--) sum += i;
Defining functions
General form of function call
(function operand ...)
Function definition (extends our understanding of define.
(define (identifier formal ...) expression ...)
Note, no types for args.
Example
> (define (add1 arg) (+ arg 1))
> (add1 4)
5
Defining functions (cont)
Void function
> (define (foo) (+ 42 99))
> (foo)
141
Binary function
> (define (bar arg1 arg2) (+ arg1 arg2))
> (bar 42 99)
141
Defining functions (cont)
Scheme is statically-scoped but dynamically typed.
Remember about dynamic typing -- one more reason to avoid set! and re-definitions.
> (define (add1 arg) (+ arg 1))
> (add1 4)
5
> (define add1 42)
> add1
42
> (add1 4)
ERROR: Wrong type to apply: 42
; in expression: (... add1 4)
; in top level environment.
Quoting and Symbols
Quote suspends evaluation
> (define a 42)
> a
42
> (quote a)
a
> 'a
a
A quoted name is called a symbol.
Can quote lists.
> (length (6 7))
ERROR: Wrong type to apply: (see errobj)
; in expression: (... 6 7)
; in top level environment.
> (length '(6 7))
2
An Aside on Literals
> (+ 4 5)
9
Literals are self-evaluating so we don't have to do this:
> (+ '4 '5)
9
Cond special form
Conditional expression general form is:
(cond clause1 clause2 ...)
where each clause is of the form:
(test expression ...)
-
tests evaluated in order till one is true
-
if true then expression is executed
-
default is
else
(else expression ...)
An example
> (cond ((> 3 2) 'greater)
((< 3 2) 'less))
greater
> (cond ((> 3 3) 'greater)
((< 3 3) 'less)
(else 'equal))
equal
Binding constructs, let
The general form of let is:
(let ((variable init)
...)
body-expression ...)
-
init expressions are evaluated in some unspecified order
-
each variable bound to appropriate value
-
body-expressions are evaluated
-
bindings in
let hide bindings in outer environments, but only after all the bindings are done.
Example of let
> (define x 42)
> (let ((x 99)
(y 101))
(+ x y))
200
> x
42
Binding constructs, let*
Similar to let, but inits are done in order and as each is performed it becomes visible in subsequent bindings.
For example, here we use the value of x to help define
y:
> (define x 42)
> (let* ((x 99)
(y (+ x 2)))
(+ x y))
200
> x
42
Demo of static scoping
Consider the following sequence of evaluations:
> (define x 42)
> (define (addx y) (+ x y))
> (addx 99)
141
If we change that value of x then we change how addx behaves:
> (define x 1)
> (addx 99)
100
But a x in a nested environment it has no effect on addx
> (define x 1)
> (let ((x 99))
(addx 99))
100
> x
1
Anonymous functions and lambda expressions
Mechanism for anonymous functions
General form of a lambda expression
(lambda (formal ...) expression ...)
An example
> (define (add2 x) (+ x 2))
> (add2 42)
44
> ((lambda (x) (+ x 2)) 42)
44
Anonymous functions as operands
Anonymous functions can be used as operands to other functions.
> (define (double proc arg) (* (proc arg) 2))
> (define (add1 x) (+ x 1))
> (double add1 4)
10
> (double (lambda (x) (+ x 1)) 4)
10
lambda compared to define
A definition of the form:
(define (variable formals) body)
is equivalent to a definition of the form:
(define variable (lambda (formals) body))
With lambda, functions are first-class types.
Can think of lambda as a function literal.
Lists
Lists are the fundamental data structure used by Scheme programs.
General form (note this is NOT a function call)
(1 2 3 4 5 6 7 8 9 10)
can have literals of any type
("cp2050" "is" "fun")
can mix literals of any type
("foo" 4 5.3)
can contain other lists
(42 ("is" "the" "answer") 5.3)
Nesting of lists can be taken to arbitrary levels
Using lists
Quote is important to suspend evaluation of list
> (define x (1 2 3))
ERROR: Wrong type to apply: (see errobj)
; in expression: (... 1 2 3)
; in top level environment.
> (define x '(1 2 3))
> x
(1 2 3)
Standard list stuff
The empty list.
()
Return a list containing the objects as its elements. None of the
objects will be evaluated.
'(obj ...)
Return true if obj is the empty list, otherwise return false.
(null? obj)
Return the first element of list. It is an error to try to get
the first element of the empty list.
(first list) or (car list)
Return list with its first element removed. It is an error to
try to get the rest of the empty list.
(rest list) or (cdr list)
Return a list with obj as its first element and list
as the rest of the elements.
(cons obj list)
Return a list having the objects as its elements.
(list obj ...)
Return the number of elements in list.
(length list)
Return a list consisting of all of the elements of the first list,
followed by all the elements of the second list, and so on.
(append list ...)
Return a list consisting of the elements of list in the
opposite order.
(reverse list)
Return the first sublist of list whose first element is
obj. The elements are compared using eqv?
(memv obj list)
Map applies proc element-wise to the elements of the
lists and returns a list of the results, in order from left to
right. That is, map first takes the first element of each list
and passes those values to proc. Then it takes the second
element of each list and passes those values to proc. And so
on, until all the elements of the lists have been used. The results
of each of the calls to proc are joined together in a list in
the order obtained and returned by the map call. The
lists must be lists, and proc must be a procedure taking as
many arguments as there are lists. If more than one list is given,
they must be the same length.
(map proc list ...)
Booleans
(not obj)
Returns #t if obj is false, otherwise #t.
(and test ...)
The test expressions are evaluated from left to right. If one
of them evaluates to false, then false is returned and no further
expressions are evaluated. If all the expressions evaluate to true,
the value of the last expression is returned.
(or test ...)
The test expressions are evaluated from left to right. If one
of them evaluates to true, then the value of that expression is
returned and no further expressions are evaluated. If all the
expressions evaluate to false, then false is returned.
Numbers
Returns true if all of its arguments are equal numerically, otherwise
returns false.
(= num ...)
Returns true if each argument is less than the argument following it,
otherwise returns false. Analogously, there are procedures >,
<= and >=.
(< num ...)
Return the maximum of the argument numbers. Analogously, there is a
procedure min.
(max num ...)
Return the sum of the argument numbers. Analogously, there are
procedures -, * and /.
(+ num ...)
Return the absolute value of num.
(abs num)
Return the value of num1 raised to the num2 power.
(expt num1 num2)
Strings
Return the number of characters in string.
(string-length string)
Return true if string1 and string2 contain the same
characters, otherwise return false.
(string=? string1 string2)
Return true if string1 is alphabetically less than
string2, otherwise return false. Analogously, there are procedures
string>?, string<=? and string>=?.
(string<? string1 string2)
Start and end are integers indicating character
positions in the string numbered from the left starting at 0. Returns
the substring of string starting at character number
start and finishing at the character before character number
end.
(substring string start end)
Return a string consisting of all the argument strings concatenated
together.
(string-append string ...)
Miscellaneous
Read a representation of a Scheme object from the user and return that
object.
(read)
Write a representation of obj to the terminal.
(display obj)
Write a newline to the terminal.
(newline)
Return true if obj1 and obj2 can be regarded as the
same object.
(eqv? obj1 obj2)
Errors
Sometimes the cause of the error will be obvious from the message:
> (-)
ERROR: -: Wrong number of args
Sometimes not, but errobj is bound to the object that caused the error
> (length (6 7))
ERROR: Wrong type to apply: (see errobj)
; in expression: (... 6 7)
; in top level environment.
> errobj
(6 7)
Tracing
Can trace functions
> (trace foo)
#<unspecified>
> (foo)
#PROCEDURE FOO CALLED
#RETURN FROM FOO
|