Introduction to List Processing and Functional Programming and Scheme

Background Material

Reading

Theme

Introduction to functional programming using scheme (a dialect of lisp). In functional programming, programs are treated as function (for every input there is a unique output. In pure functional programming there are no variables and hence no assignment or side effects. There are function definitions and function applications and the value of a function only depends on the values of its arguments (referential transparency). Furthermore, the value of a function can not depend on the order in which its arguments are evaluated. A consequence of not having variables is that there are no loops; repeated operations and other control flow is obtained through recursion. Finally, functions are treated as first class values, i.e. functions are viewed as values which can be computed by other functions and passed as parameters to functions.

The lack of assignment and referential transparancy make the semantics of functional programs straightforward. There is no state, there is no concept of memory locations and pointers, only bindings of names to values - once a name enters the environment its value can never change. Such semantics is called value semantics. While this seems like a very restricted type of programming, it can be shown to be turing complete and hence, theoretically, as powerful as programs with loops and variables.

In practice functional languages, like lisp, scheme, and ML, are not pure; however, they are conducive to techniques that arise in pure functional programming and consequently provide the programmer with a different, and at times very useful, view of programming.

Topics

  1. S-Expressions and Evaluation Rules

    Programs and data are the same - in scheme they are lists - the only difference is how they are interpreted. Given a list, S-Expression, (E1 E2 ... En), its value is obtained by recursively evaluating the S-Expressions E1, E2,...,En and applying the value of E1, which should be a function, with its arguments bound to the values of E2,...,En. In the base case, atoms, evaluate to themselves. This evaluation process is called applicative normal order. For example

  2. List processing functions

    Syntactically lists are enclosed in parentheses with elements separated by spaces - (a1 ... am). Elements of a list may be lists themselves. The null list is denoted by (). The null list is detected by the predicate null? - a predicate is a function that returns true or false (in scheme denoted by #t and #f respectively). Note that the null list is considered both a list and an atom and evaluates to itself. Given a set of atoms, all lists, of arbitrary order (i.e. nesting) can be built from () and the function (cons x y), which constructs a list whose first element is x and remaining elements are the elements of y. Technically y need not be a list (to be discussed below). The first element of a list is obtained with the function car and the remaining elements with the function cdr.

    Many other list processing functions are available (see chapter 7 of the scheme reference manual that is part of the MIT-Scheme distribution): E.G. append, length and reverse. These functions, and many others, can be built from the primitive list processing functions car, cdr, null? and cons using recursion.
  3. Lambda expressions

    A function in scheme is denoted by (lambda name (formal-parameters) body), and is called a lambda expression. Lambda expressions evaluate to a function which can then be applied.

  4. Conditional expressions
  5. Recursion

    Since there are no side effects in functional programming, functions can be thought of as mathematical definitions and the use of recursive definitions and recursion allows us to define and hence implement many useful functions. Moreover, without side effects, there can be no loops since loops require a loop index which is incremented. Hence, in a pure functional language, all control must be done by recursion. Note that scheme is not a pure functional language, i.e. there are variables, however, we will focus on the functional programming style and use recursion for control.

  6. Higher-order functions

    Functions that take functions as parameters and functions that return functions.

Sample Functions

The list processing functions length, concat, numints, and order are provided. The length function returns the number of elements in a list and the concat returns the concatenation of two lists. The function numints returns the number of integers in an list of arbitrary order. Since the list may contain elements that are themselves lists, the function must be called recursively on both the car of the list and the cdr of the list. Such a function is said to have deep recursion. The functions length and concat only recurse on the cdr of the list and are said to have shallow recursion. Note that the built-in functions eq? and equal? for checking to see of two objects are equal differ in that eq? uses shallow recursion and equal? uses deep recursion. The function order computes the order of an object, which is equal to zero if the object is an atom and 1 plus the maximum order of its elements if it is a list. The implementation of order uses reduce and map.

Lecture Notes

References

Exercises

Created: April 16, 2008 (modified April 19, 2012) by jjohnson AT cs DOT drexel DOT DOT edu