logo
 
LISP/Scheme
CptS 355 - Programming Language Design
Washington State University
Home
Calendar
Syllabus
Resources
People
Project turn-in

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
  • start with letter, then any of the following
    + - . * / < = > ! ? : $ % _ ~ ^
    
  • case-insensitive
  • ; this is a comment

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

  • general form
    (if test true-expression false-expression)
    
  • alternate form (false branch returns #f)
    (if test consequent)
    
  • examples
        > (if (> 5 4) 42 99)
        42
        > (if (< 5 4) 42 99)
        99
        > (if (> 5 0) 33)
        33
    

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 

                                                                                                                                                                                                                                                                                                                                             
  (c) 2003 Curtis Dyreson, (c) 2004, 2005 Carl H. Hauser           E-mail questions or comments to Prof. Carl Hauser