A programming paradigm is a general approach, orientation, or philosophy of programming that can be used when implementing a program. One paradigm may be generally better for certain kinds of problems than other paradigms are for those problems, and a paradigm may be better for certain kinds of problems than for other kinds of problems. In this section we will provide an overview of the four most common paradigms: imperative (or procedural), applicative (or functional), rule-based (or logic), and object oriented.
If a programming language is well suited (or at least designed to be well suited) for a particular paradigm, the language is said to be a language of that paradigm. For example, C is considered to be an imperative language, ML is considered to be an applicative language, Smalltalk is considered to be an object-oriented language, and Prolog is considered to be a logic programming language.
In the imperative, or procedural (the two terms are usually interchangeable), paradigm, the primary orientation is toward variables and the sequential execution of statements that change the values of those variables. The imperative paradigm corresponds closely to the organization of a standard (Von Neumann) computer. The state of a computation is defined by the current values of all of the variables of the program (including external files and also possibly also including the values to be assumed by uninitialized variables during program execution), the location of the next statement to be executed, and the internal data needed to return from subprogram calls that are currently active. The execution of each statement changes these values, and hence changes the state. Thus the execution of an imperative program can be viewed as the execution of a sequence of statements
S1;where each statement Si is a function
S2;
S3;
. . .
Sn;
Si: state -> stateIf we let Si be the state after statement Si executes, then we also can view the execution of an imperative program as a sequence of states
S0, S1, S2, S3, ..., Sn-1, Snwhere S0 is the initial state before program execution begins. Finally, then, the notion that each statement Si is a function that maps the state when it begins to execute into the state when it finishes execution can be expressed in functional notation as
Si(Si-1) = SiThat is, the execution of statement i transforms state i-1 into state i.
You may wonder what all of this has to do with writing a program to do something worthwhile. Making the observations that we have just concluded about an imperative program and viewing its execution of statements as a sequence of function executions that map states to other states does not provide any magic help in writing better programs, but if you take it seriously it does help develop an overall feel for what is happening when programs execute, which will in turn help you to produce better programs. We also will make use of these notions later to relate imperative programs to functional programs.
Imperative programming languages include C, Ada, Pascal, Fortran, and Cobol. Thus imperative languages are probably the most widely used languages, even though the object-oriented languages are gaining in popularity.
The primary orientation of the applicative/functional paradigm is toward values (as opposed to variables for the imperative paradigm) and the evaluation of expressions (as opposed to the execution of statements in the imperative paradigm). The execution of a program is essentially the evaluation of an expression, which is usually a function applied to an argument list. Successive states are not of interest in the functional paradigm. Each value that is produced as the result of an intermediate evaluation is used in later evaluations, rather than being saved as the value of a program variable.
As you probably have experienced in working on your ML programs, when you are used to thinking in terms of the imperative paradigm it can be difficult to see how to do what you want to do using an applicative approach. We now look at a view of the relationship between the imperative and applicative paradigms, which can sometimes help in determining how to structure a functional program.
To build upon the notion of an imperative program as a sequence of state transitions, and to relate this to the notion that a program is just a mapping of some initial state S0 into a final state Sn, let F be the function defined by an imperative program whose execution consists of executing the n statements
S1; S2; S3; . . . ; Sn;so that the program execution is just the function evaluation
F(S0) = Snthat maps the initial state into the final state.
Now think about the function represented by a sequence of two statements. For example, we can think of the combination of executing statement S1 followed by the execution of statement S2 as a function that transforms state S0 to state S2. If we denote by S1-2 the function that represents the combined computation of S1 followed by S2, and also note that the input state for statement S2 is the output from statement S1, then we have
S1-2(S0) = S2(S1(S0)) = S2or S1-2 = S2 o S1. Extending this analysis to the sequence of statement executions for the entire program whose function we denoted by F, we have
F(S0) = Sn(Sn-1(...(S2(S1(S0))...)) = Snor
F = Sn o Sn-1 o ... o S2 o S1
So, in some sense we can think of transforming an imperative program into an equivalent applicative program by composing the functions represented by each statement of the imperative program in the reverse of execution order.
Some of the most widely-used applicative languages are Lisp (in various dialects), ML, and Scheme. Although applicative languages are not nearly so widely used as imperative and object-oriented languages, they are widely used in certain communities, such as artificial intelligence. ML has even been used to implement an operating system at Carnegie Mellon University. Applicative languages such as ML and Scheme have extensive data structuring and creation facilities that are very similar to those found in object-oriented languages.
In a rule-based language, programs are constructed by defining a set of rules for various goals. These rules essentially define logical expressions whose value is true. A "program" is a search for some combination of rule applications that results in a valid logical expression (an expression that is true).
For example, in a language like Prolog we could define function max(X, Y, R) that determines the maximum of two values X and Y by
max (X, Y, R) -> pre_max(X, Y) , post_max (X, Y, R).In Prolog, the comma (,) is an operator that denotes logical AND, and the semicolon (;) is an operator that denotes logical OR. So the above definition for max says that the value of max(X, Y, R) is true if pre_max(X, Y) is true and post_max(X, Y, R) is true. The rule for pre_max(X, Y) says that pre_max(X, Y) is true if integer(X) and integer(Y) are both true, where integer is a predefined predicate function that is true if its operand is of type integer and is false otherwise. Thus for max(X, Y, R) to be true, X and Y must be integers and post_max(X, Y, R) must be true.pre_max (X, Y) -> integer(X), integer(Y).
post_max(X, Y, R) -> (R=X ; R=Y), R>=X, R>=Y.
The last rule says that for post_max(X, Y, R) to be true, R is equal to X or equal to Y, and R is greater than or equal to both X and Y. Note that the definition of max would not be valid if post_max were defined as
Having made these definitions, we can then interact with the Prolog system in a manner similar to interacting with sml. An example of some interactions, where the prompt is a question mark (?), is shown below.
? max (3, 5, 7).
no
? max (3, 5, 3).
no
? max (3, 5, 5).
yes
? max (8, 5, X).
yes, X=8
When we entered the expression max(3, 5, 7)., the system responded "no", indicating that max(3, 5, 7) is false. The first three expressions illustrate that if we enter an expression that can be evaluated (i.e., if the values of the parameters are known), then the expression is evaluated and a result of true (yes) or false (no) is obtained. The last expression (max (8, 5, X)), illustrates that if there is a variable for one of the parameters, then Prolog tries to find a value that makes the expression true, and if it can then it reports that value (or values).
A "program" in Prolog is just a sequence of expressions that are ANDed together. For example, the following is a program to read two integers and print their maximum:
write('Enter first number: '), read(X),
write('Enter second number: '), read(Y),
write('The max of '), write(X),
write(' and '), write(Y),
write(' is '),
max(X, Y, R),
write(R).
Rule-based languages enjoyed a surge of popularity during the 1980s, but recently their usage has declined. They are well-suited to knowledge-oriented applications such as medical or other problem diagnosis systems.
In the object-oriented paradigm the focus is on objects that represent either data or computational entities used in a program. One of the key features is a mechanism for encapsulating data values representing the value of an object with the operations that can be applied to the object. This is an extension of the usual notion of type, and in an object-oriented language such an encapsulation is called a class. Another key feature is the ability to add additional component data values and operations to an existing class of objects in order to obtain a new class of objects.
As an example illustrating a computational object, consider a random number generator. In an imperative or applicative language a random number generator would be a function, say Random, where the value of Random() is a (new) random number. In an object-oriented language we might instead define a class Random with the operation next. Then we could create a (new) random number generator by a statement such as one of the following:
| Code | Language |
|---|---|
| Random myRandom; | C++ |
| Random myRandom = new Random(); | Java |
| myRandom := Random new. | Smalltalk |
| Code | Language |
|---|---|
| myRandom.next() | Java and C++ |
| myRandom next | Smalltalk |
(Of course we would have to do something with the result returned by the method next, such as assigning the random number to a variable or using it in a computation, in order for these constructs to be useful.)
The definition of classes, including the declaration of the data components and the definition of the methods, is the central focus of an object-oriented program. The code that defines a method is usually written in an imperative, or perhaps applicative, style.
Some object-oriented languages in use today are C++, Eiffel, Java, and Smalltalk. Object-oriented languages are increasing in popularity, and many people believe that they will become the most widely-used languages. Many, if not most, knowledgeable computing professionals recognize, however, that the object-oriented paradigm is not the best one for every problem, and that we will always have the need for a variety of paradigms for implementing solutions to problems using computers.
Do the Exercises for P&Z Chapter 2.