
Appendix B FUNCTIONAL PROGRAMMING WITH SCHEME he languages usually studied in computer science—namely, Pascal, C, Modula-2, and Ada—are considered imperative languages because the T basic construct is a command. These languages are heavily influenced by the “von Neumann architecture” of computers, which includes a store (memory) and an instruction counter used to identify the next instruction to be fetched from the store. The computation model has control structures that determine the sequencing of instructions, which use assignments to make incremental modifications to the store. Imperative languages are characterized by the following properties: • The principal operation is the assignment of values to variables. • Programs are command oriented, and they carry out algorithms with state- ment level sequence control, usually by selection and repetition. • Programs are organized as blocks, and data control is dominated by scope rules. • Computing is done by effect, namely by changes to the store. The computing by effect intrinsic to imperative programming plays havoc with some of the mathematical properties that are essential to proving the correctness of programs. For example, is addition commutative in an im- perative program? Does “write(a+b)” always produce the same value as “write(b+a)”? Consider the following Pascal program: program P (output); var b : integer; function a : integer; begin b := b+2; a := 5 end; begin b := 10 write(a+b) or write(b+a) end. In fact, implementations of Pascal will most likely give different results for the two versions of this program, depending on the order of evaluation of 587 588 APPENDIX B FUNCTIONAL PROGRAMMING WITH SCHEME expressions. This anomaly is caused by the side effect in the expression be- ing evaluated, but programming by effect lies at the heart of imperative pro- gramming. If we depend on imperative programs, we must discard many of the basic properties of mathematics, such as associative and commuative laws of addition and multiplication and the distributive law for multiplica- tion over addition. The functional programming paradigm provides an alternative notion of pro- gramming that avoids the problems of side effects. Functional languages are concerned with data objects and values instead of variables. Values are bound to identifiers, but once made, these bindings cannot change. The principal operation is function application. Functions are treated as first-class objects that may be stored in data structures, passed as parameters, and returned as function results. A functional language supplies primitive functions, and the programmer uses function constructors to define new functions. Pro- gram execution consists of the evaluation of an expression, and sequence control depends primarily on selection and recursion. A pure functional lan- guage has no assignment command; values are communicated by the use of parameters to functions. These restrictions enforce a discipline on the pro- grammer that avoids side effects. We say that functional languages are refer- entially transparent. Principle of Refer ential T ranspar ency: The value of a function is deter- mined by the values of its arguments and the context in which the function application appears, and it is independent of the history of the execution. ❚ Since the evaluation of a function with the same argument produces the same value every time that it is invoked, an expression will produce the same value each time it is evaluated in a given context. Referential transparency guarantees the validity of the property of substituting equals for equals. Lisp Work on Lisp (List processing) started in 1956 with an artificial intelligence group at MIT under John McCarthy. The language was implemented by McCarthy and his students in 1960 on an IBM 704, which also had the first Fortran implementation. Lisp was an early example of interactive comput- ing, which played a substantial role in its popularity. The original develop- ment of Lisp used S-expressions (S standing for symbolic language) with the intention of developing an Algol-like version (Lisp 2) with M-expressions (M for metalanguage). When a Lisp interpreter was written in Lisp with S-ex- pressions, Lisp 2 was dropped. The principal versions, which are based on Lisp 1.5, include Interlisp, Franz Lisp, MacLisp, Common Lisp, and Scheme. SCHEME SYNTAX 589 Lisp has a high-level notation for lists. Functions are defined as expressions, and repetitive tasks are performed mostly by recursion. Parameters are passed to functions by value. A Lisp program consists of a set of function definitions followed by a list of expressions that may include function evaluations. Scheme Syntax The Scheme version of Lisp has been chosen here because of its small size and its uniform treatment of functions. In this appendix we introduce the fundamentals of functional programming in Scheme. When we say Scheme, we are referring to Lisp. The basic objects in Scheme, called S-expressions, consist of atoms and “dotted pairs”: <S-expr> ::= <atom> | ( <S-expr> . <S-expr> ) The only terminal symbols in these productions are the parentheses and the dot (period). The important characteristic of an S-expression is that it is an atom or a pair of S-expressions. The syntactic representation of a pair is not crucial to the basic notion of constructing pairs. Atoms serve as the elementary objects in Scheme. They are considered indi- visible with no internal structure. <atom> ::= <literal atom> | <numeric atom> <literal atom> ::= <letter> | <literal atom> <letter> | <literal atom> <digit> <numeric atom> ::= <numeral> | – <numeral> <numeral> ::= <digit> | <numeral> <digit> Literal atoms consist of a string of alphanumeric characters usually starting with a letter. Most Lisp systems allow any special characters in literal atoms as long as they cannot be confused with numbers. The numeric atoms de- fined here represent only integers, but most Lisp systems allow floating-point numeric atoms. Since S-expressions can have arbitrary nesting when pairs are constructed, Scheme programmers rely on a graphical representation of S-expressions to display their structure. Consider the following diagrams illustrating the S- expression (a . (b. c)): Lisp tree (or L-tree): a b c 590 APPENDIX B FUNCTIONAL PROGRAMMING WITH SCHEME Cell diagram (or box notation): a b c We prefer using the box notation for S-expressions. Atoms are represented as themselves, and if the same atom is used twice in an S-expression, a single value can be shared since atoms have unique occurrences in S-ex- pressions. Functions on S-expressions The simplicity of Scheme (and Lisp) derives from its dependence on several basic functions for constructing pairs and selecting components of a pair. Two selector functions are used to investigate a pair: car Applied to a nonatomic S-expression, car returns the left part. cdr Applied to a nonatomic S-expression, cdr returns the right part. On the IBM 704, car stood for “contents of address register” and cdr for “con- tents of decrement register”. Some authors have suggested that “head” and “tail” or “first” and “rest” are more suggestive names for these functions, but most Lisp programmers still use the traditional names. The following examples that use brackets [ ] to delimit arguments do not follow correct Scheme syntax, which will be introduced shortly: car [ ((a . b) . c) ] = (a . b) cdr [ ((a . b) . c) ] = c An error results if either function is applied to an atom. An abstract implementation of the selector functions can be explained in terms of a box diagram: car returns the left pointer. cdr returns the right pointer. c a b LISTS IN SCHEME 591 A single constructor function cons builds a pair given two S-expressions: cons Applied to two S-expressions, cons returns a dotted pair contain- ing them. For example: cons[ p , q ] = (p . q) cons[ (a . b) , (a . c) ] = ((a . b) . (a . c)) As an abstract implementation, we allocate a new cell and set its left and right components to point to the two arguments. Observe that the atom a is shared by the two pairs. (a . b) cons [ (a . b) , (a . c) ] a b (a . c) c Lists in Scheme The notion of an S-expression is too general for most computing tasks, so Scheme primarily deals with a subset of the S-expressions. A list in Scheme is an S-expression with one of two forms: 1. The special atom ( ) is a list representing the empty list. Note that ( ) is the only S-expression that is both an atom and a list. 2. A dotted pair is a list if its right (cdr) element is a list. S-expressions that are lists use special notation: (a . ( )) is represented by (a) (b . (a . ( ))) is represented by (b a) (c . (b . (a . ( )))) is represented by (c b a) Cell diagrams for lists are usually drawn with a horizontal “spine” that stretches from left to right. The spine contains as many boxes as the list has elements at its top level. 592 APPENDIX B FUNCTIONAL PROGRAMMING WITH SCHEME (a b c) abc( ) ((a b) (c) d) = ((a . (b . ( ))) . ((c . ( )) . (d . ( )))) d a b c Observe the abbreviation of a slash through the cell at the end of a list to represent a pointer to an empty list ( ). The elementary constructor and selectors for S-expressions have special prop- erties when applied to lists. car When applied to a nonempty list, car returns the first element of the list. cdr When applied to a nonempty list, cdr returns a copy of the list with the first element removed. cons When applied to an arbitrary S-expression and a list, cons re- turns the list obtained by appending the first argument onto the beginning of the list (the second argument). For example: car [ (a b c) ] = a cdr [ (a b c) ] = (b c) car [ ((a)) ] = (a) cdr [ ((a)) ] = ( ) cons [(a) , (b c) ] = ((a) b c) cons [ a , ( ) ] = (a) Syntax for Functions In Scheme, the application of a function to a set of arguments is expressed as a list: (function-name sequence-of-arguments) This prefix notation is known as Cambridge Polish For m since it was devel- oped at MIT in Cambridge.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages24 Page
-
File Size-