Programming Languages (CSC-302 98S)
Outline of Class 21: Functional Programming: The Scheme Perspective
Held: Wednesday, March 11, 1998
- LISP (for LISt Processing language)
was developed by John McCarthy of MIT in the late 1950's as a language
for artificial intelligence programming.
- It is considered one of the primary functional languages and has
many descendants.
- 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
- built-in lists and list-manipulation operations
- numbers and strings as basic data types
- 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
- 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.
- 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.
- This is similar to call-by-name.
- 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 which 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 which 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).
- 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
or null?.
- 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, one can build longer lists with
list.
- 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 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 formal semantics (the original LISP was informally defined).
- 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
- In Scheme, we define "global" variables with
(define id exp).
- Similarly, we 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 any prompt.
Compute the factorial of n
(define (fact n)
(if (= n 0) 1 (* n fact (- n 1))))
Add an element to the end of a list
(define (addend x l)
(if (null? l)
(list x)
(cons (car l) (addend x (cdr l)))))
Reverse a list (yes, it's built-in, but we can still define it)
(define (reverse l)
(if (null? l)
()
(addend (car l) (reverse (cdr l)))))
A Tail-recursive reverse
(define (tailrev l r)
(if (null? l)
r
(tailrev (cdr l) (cons (car l) r))))
(define (reverse l)
(tailrev l ()))