Class 05: Untyped Functional Programming in Scheme
Held Wednesday, February 3
Summary
Contents
- LISP (for LISt Processing language)
was developed by John McCarthy of MIT in the late 1950's as a language
for artificial intelligence programming.
- At the time LISP was developed, many researchers suggested that
intelligence involved
- symbolic manipulation and
- organization of symbols in hierarchical structures.
- In developing a language to support ``intelligence'', McCarthy chose to
include
- symbols as basic data types (supporting symbolic manipulation);
- built-in heterogeneous lists and list-manipulation operations
(permitting the construction and manipulation of hierarchical
structures);
- numbers and strings as basic data types (yes, we do math, too);
and
- functions as a basic data type (yes, he read Church).
- Noting that the application of a function to arguments is similar to
listing the function and its arguments,
McCarthy also chose to use the same notation for function applications
and list structures.
- An open paren
- The elements of the list (or the function and its arguments), separated
by spaces
- A close paren
- For example, consider
(a b c).
- In one context, this is the list of three elements,
a, b, and c.
- In another context, this is the application of the function
a to two arguments, b and c.
- This is an extreme application of von Neumann's assertion that programs
are data: in LISP, the same structure can be treated as a list at one
point and a function application at another.
- McCarthy chose to use a prefix notation for all function operations,
including ``primitive'' operations. In LISP, we write
(+ 2 3)
for ``add two and three''.
- LISP is often interpreted. Most LISP interpreters use the
read-eval-print model. That is, the interpreter
- reads an input expression or function definition;
- evaluates the expression; and
- prints the result.
- In LISP, function order is done in applicative (also known
as eager or innermost) order: before functions
are applied to their arguments, those arguments are evaluated.
- It is often helpful to classify the types of functions we use in
writing LISP programs.
- Constructors build data structures.
- The most common constructor is
cons which builds lists.
- Destructors and Selectors extract
components from data structures.
- The most common selectors are
head which selects the first element of a list and
tail which selects all but the first element.
- Predicates return boolean values.
- Most predicates check types or attributes of their arguments.
For example,
atom? checks whether something is an atom.
- The power of McCarthy's design is demonstrated by LISP's longevity.
LISP and LISP variants are still dominant languages for AI programming
(and for other types of programming).
(How's that for alliteration?)
- Most (all?) LISP-like languages provide a number of functions for
building and modifying lists.
- LISP builds its lists with cons cells, pairs of pointers.
- The first pointer gives the data.
- The second pointer gives the rest of the list.
- The empty list is
nil or ().
- The predicate that checks if a list is empty is usually
nullP
(in LISP) or null? (in Scheme).
- One constructs lists with
cons.
(cons x x) builds a list
with first element x and remainder
xs.
- Those of you who like to think about what's happening in memory can
think of this as allocating a new cons cell whose left pointer is
the object and whose right pointer is the list.
- Those of you who prefer a higher-level view can think of this as a
form of ``prepend''.
- In many variants of LISP, one can build longer lists with
list.
-
(list a b c) produces the same result as
(cons a (cons b (cons c nil)))
- You can get the first element of a list with
(car list).
- You can get the all but the first element of a list with
(cdr list).
- Why
car and cdr? Because in the original
machine LISP was implemented on, it was easy to load the cons cell into
the address and data registers. ``car'' is ``contents of
address register''. ``cdr" is "contents of data register''.
- In many LISP-like languages,
car is called head
and cdr is called tail.
- Scheme is a variant of LISP that "cleans up" some of the problems of
the original LISP.
- Scheme provides
- Static scoping (the original LISP was dynamically scoped).
- A small formal semantics (the original LISP was informally defined; a
later formal semantics took hundreds of pages).
- Streams, a form of ``infinite'' lists (which had been suggested
as an add-on to the original LISP).
- A way to package up ``the rest of the program''. These are
called continuations.
- A host of other features.
- Scheme is now defined by committee. The most current definition
is the fifth revision, which has been a few years in the making.
- Scheme provides a number of basic operations
- Mathematics
- More predicates than you can shake a stick at.
- Simple conditionals, such as
(if test then-part else-part)
- An expanded conditional which bears some resemblance to the guarded
conditional (but the guards are executed in order)
(cond (
(test1 exp1)
(test2 exp2)
...
(testn expn)
))
- In Scheme, we define ``global'' variables with
(define id exp).
- Similarly, we can define ``global'' functions with
(define (fun param1 ... paramn) exp)
- We use a commercial version of Scheme in the MathLAN, Chez Scheme.
- Chez Scheme provides a simple interactive interface which you can
get by typing
scheme at a Unix prompt.
;;; Compute the factorial of n.
(define (fact n)
(if (= n 0) 1 (* n (fact (- n 1)))))
;;; Add x to the end of list l.
(define (addend x l)
(if (null? l)
(list x)
(cons (car l) (addend x (cdr l)))))
;;; Reverse the list l.
;;; Yes, this function is a standard Scheme function, but it is
;;; helpful to consider how we might write it.
(define (reverse l)
(if (null? l)
()
(addend (car l) (reverse (cdr l)))))
;;; Here is an attempt to write a more efficient reverse function.
;;; Create the list of the elements of l in reverse order followed
;;; by the elements of r. For example, (tailrev '(a b c) '(3 2 1))
;;; will create the list (c b a 3 2 1).
(define (tailrev l r)
(if (null? l)
r
(tailrev (cdr l) (cons (car l) r))))
;;; Reverse the list l, using our new helper function.
(define (reverse l)
(tailrev l ()))
We experimented with the functions given above.
History
- Created Tuesday, January 19, 1999 as a blank outline.
- Filled in the details on Wednesday, February 3, 1999. Based primarily
on outline 21 from last year's class.
- Removed some details after class.