Programming Languages (CSC-302 98S)

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

# Outline of Class 21: Functional Programming: The Scheme Perspective

Held: Wednesday, March 11, 1998

## An Introduction to LISP-Like Languages

• 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).

### Lists in LISP-Like Languages

• 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`.

## An Introduction to Scheme

• 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
• Mathematics
• More predicates than you can shake a stick at.
• Simple conditionals `(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 define "global" functions with `(define (fun param1 ... paramn) exp)`

### Scheme in the MathLAN

• 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.

### A Few Scheme Programs

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 ()))
```

## Higher-Order Functions

• One of the more important aspects of functional programming languages is the ability to write so-called higher order functions. These are functions that take functions as arguments or build and return functions.
• Why are higher order functions useful? For a number of reasons, most of which relate to good program design.
• For example, good program design suggest that when you have multiple functions that have a similar structure, you build general functions that extract out the common parts.
• Consider these two related functions (each with an extra helper function):
```;;; Square a value
(define (square x) (* x x))
;;; Square all the elements of a list
(define (squareall l)
(if (null? l) nil
(cons (square (car l)) (squareall (cdr l)))))
;;; Add one to a value
(define (increment x) (+ x 1))
;;; Increment all the elements of a list
(define (incrementall l)
(if (null? l) nil
(cons (increment (car l)) (incrementall (cdr l)))))
```

• What's the "common part"?
• We test whether the parameter is null.
• If so, we return nil.
• If not, we apply a function to the car and then recurse on the rest.
• So, why not write a function that does just that
```;;; Apply a function to each element of a list
(define (applyall fun l)
(if (null? l) nil
(cons (fun (car l)) (applyall fun (cdr l)))))
```

On to Other Issues in Functional Languages and Scheme
Back to Introduction to Functional Programming
Outlines: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
Current position in syllabus

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.