You are on page 1of 11
Structured Programming (1990) 11: 105-115 ‘Structured Programming {©1990 Springer-Verlag New York Ine Programming Languages as Thought Models Peter Rechenberg Institut fr Informatik, Johannes Kepler University of Linz, Alienbergerstcasse 69, A-4040 Linz, Austria Abstract, In this paper comtemporary programming, languages are compared and classified according to their “thought model", ie., the distinguishing features of 2 family of programming languages that influence the way programmers think while writing or reading programs in this Innguage family. Considered are sequential imperative languages (subdivided into algorithm-oriented and object-oriented) and nonimperative languages (subdivided into functional and logical). The distinguishing concepts of variable and state arc stressed and the application areas of the different concepts are outlined. This leads to a partition of programming languages into value-oriented and object-oriented languages. It is claimed that the concept of algorithm as the fundament of computer science should be replaced by fone that models perpetual processes and their ‘communication. The paper does not consider parallel languages and combinations of the different thought models. Key Words: programming languages, programming paradigms, paradigms of programming 1. Introduction and Overview There are lots of books dealing with a single programming tanguage and also some tying together the principles of a larger class of different programming languages, notably those of Pratt (7] and Ghezzi and Jazayeri (6). Languages may be described and compared according to various viewpoints, such as syntactical, control-oriemted, data-oriented, abstraction-oriented, implementation-oriented and application-oriented. This, paper looks at contempory programming languages with regard to the way of thinking which they impose on their users. It is now fashionable to call this viewpoint paradigm-oriented, but in my opinion this is an opaque and misleading term, so I prefer to speak of the thoughe models or thinking habits that distinguish classes of programming languages. a a= a Algorithm Object. Functional — Logic Se SL a =f 7 Figure 1, A classification of programming languages. Figure 1 shows a classification of the programming languages according to their thought models. First of all ‘one may divide the languages into two large groups: the imperative (also called procedural) and the nonimperative (Glso called declarative). Imperative languages are based roughly on a thought ‘model that regards a computation as a process in time. ‘The set of values of all variables at a given point in time constitutes the state of the computation, and the execution of the program is a transformation of states over time, starting with an initial state and ending with a final state. Nearly all of our languages for numerical, data processing and software engineering work, from Basic to Ada, ae of this kind, ‘The imperative languages may be further divided into algorithm-oriented and object-oriented languages. In algorithm-oriented languages there is a clear-cut distinction between algorithm and data. A program is seen as an algorithm that transforms input values into ‘output values. Here the algorithm (as the state- ‘transformation rule) receives primary attention while the ta stay in the background. In object-oriented languages data_and algorithms are united to objects. The algorithms are merely seen as interactions between objects. The execution of an object-oriented program can be viewed as a simulation of a scenario in a real or 106 Rechenberg: Programming Languages as Thought Models abstract world, where objects (possibly of a very complex structure) interact, transforming existing objects, creating new objects and destroying uscless objects. Some languages, like Modula-2 and Ada, have properties of both algorithm-orientedness and object- orientedness. ‘Nonimperative languages turn away from the state transformation view and replace it with other concepts. Functional languages build their thought model on the concept of a mathematical function, which is timeless and side-effect free, avoiding many subtleties of imperative languages bat introducing other concepts. Logical languages build their thought model on the ‘concept of logical relations in the sense that the result of x+y may be defined by a relation Add(x, y, 2) with the meaning “ is the result of the addition of x and y”. Relations may be true or false. If x and y are variables ‘with numerical values and z is a variable with no value yet, an algorithm tries to assign to z such a value that the relation Adar, y, 2) will become true, This thought ‘model is also side-effect free and does not rely on the idea of state. Since a program in functional and logical Tanguages looks more like the specification of the problem to be solved than as a description of the solution, these languages are also called declarative. As will be shown, this is only a name and desire, not a fact. Contrary to claims found in the literature, programmers working with declarative languages must ‘also exhibit algorithmic thinking to make their ‘Programs work correctly. In the rest of this paper I will characterize the thought models in order to give a clear understanding of their scope and their limits. ‘All of these thought models are mainly used to express sequential computations, but they are also suited—at least in theory—io express parallel compu- tations. This viewpoint will not be considered. 2. The Imperative Thought Model ‘The imperative thought model relies on a fairly realistic ‘computer model, in particular on the “von Neumann machine”. Its central concept is that of a variable as a container of values, The real computer has a store, consisting of a linear sequence of cells that are identified by addresses. A variable, as defined and used in a program, is associated with a storage area. There are three items that collectively determine a variable: name, address, value (Figure 2). In the source program 2 variable is expressed by its name; during program execution it has a fixed address in store by which it is ind it contains a value, The value may change by assignment, resulting in a time sequence of values representing the history of a variable during a computation, radius 1s name: radius address: 1234 value: 20 Figure 2, Name, address, and vale make up a variable. ‘There is a “magic formula”, expressing this concept most concisely: mxtl. ‘What are the problems with this model? They stem from two effects: 1. the use of different names for the same storage arca (sharing, alias effect) 2, the use of names for variables in the source program with different meanings. The name x may denote + the value of the variable x (in value computing expressions), + the address of the variable x (in declarations and ‘on the left-hand side of assignment statements), or + aveference to another variable (if x is a pointer variable). To make things worse, these two effects may interfere, Even if the language does not allow explicit superposition of variables, two different pointer variables may point to the same arca, producing the alias effect. As a result, the name of a variable may have three different meanings, depending on the semantics of the programming language. According to Cleaveland [4] they may be called value semantics, storage semantics and pointer semantics. With value semantics, a variable is a value. There is no concept of storage or state changing or sharing involved. With storage semantics, a variable is a storage area, which contains a value. With pointer semantics, a variable is a pointer to a storage area, which contains a value. Imperative languages usually bring all three kinds of semantics into action. A non-pointer variable in an expression behaves according to value semantics; a non- pointer variable in a declaration or on the left-hand side of an assignment behaves according to storage sem: tics. For pointer variables things are yet more compli- cated because a pointer variable p sometimes denotes a reference to an object (concrete denotation) and some- times denotes the object to which it points (abstract Rechenberg: Programming Languages as Thought Models, 107 denotation). Let p and q be pointers to a record of type R and let p currently point to x and q to y (both of type R). Now consider the assignment q =p. In the concrete denotation this means pointer assignment, such that afterwards p and q both point to x. In the abstract denotation it means the assignment of x to y, which is ‘completely another thing. Sharing provokes the inadvertent and invisible change of the value of a variable while assigning to another one. Staring and the Janus-faced denotation of ‘variables make program writing error-prone and program understanding difficult, Both algorithm-oriented and object-oriented imperative languages benefit and suffer from this concept of a variable, 2.1. The Algorithm-Oriented Imperative Model ‘The algorithm-oriented model is based on the idea of an algorithm that transforms input data into output data. It may also be called the Turing machine approach, the automata-theoretical approach or the state changing ‘approach. Itmay be characterized by the following facts: + A-computation is a stepwise process in time which uses data. The algorithm and its control are in the foreground, the data in the background. The algorithm is primary and permanent; the data are secondary and transient. + A computation is (at least in principle) an autonomous process in time. It starts with some ‘npot data and transforms them into outpat data, but there is no influence of the outside world between start and stop. The output data are completely determined by the algorithm and the input data Consequently, input and output during the computation, i-c., man-machine interaction, is not contained in the pure form of this model. + Acomputation consists ofa finite sequence of steps, stopping if it has produced the desired output data, ‘Note that applications in which programs are only part of a larger system, especially part of man-machine systems (as in operating systems, data base systems, office automation systems, simulation and control of traffic, plants and so on), are not of this kind, They do not produce results and they must provide for infinite looping. This thought model is the best known of all. It governs most languages from Fortran to Pascal to Ada, ‘Although the coarse model of all these is the same, the languages differ in many respects. Thinking in Fortran means thinking in a world consisting of static storage allocation, only simple data structures, and independent compilation of procedures. Pascal adds dynamic storage allocation, highly structured and user-defined data types, recursion and pointers. Modula-2 and Ada add data type abstraction and parallelism, in Modula-2 in the low- Jevel form of coroutines, in Ada in the higher form of ‘communicating sequential processes. Ada also adds generic modules and exception handling. Each of these features modifies the underlying thought model to some ‘extent, but all of them are instances of the algorithm ‘oriented imperative model. ‘There are also some languages of this kind that crane their neck into a higher-level world. In APL one can apply very complex operators to very complex data structures, omitting a lot of control (e.g., looping). In Snobol4 there is a special kind of data and control structure called pattern, which may be used to define very high level operations on character strings (¢.g., ‘complete syntax analysis). Both are attempts to elevate thinking and reasoning about programs to a more abstract level, also permitting a more concise program notation, ‘Module-oricnted languages, particularly Modula-2 and ‘Ada, have properties which bring them closer to object- ‘oriented languages. A module permits the definition of ‘an abstract data type, thus anticipating a central concept of object-oriented languages, but the module is not reusable as in object-oriented languages. 2.2, The Object-Oriented Imperative Thought Model There are a lot of applications where the algorithm- oriented thought mode! in its purity is not appropiate. Tn database systems the data are the persistent entities. ‘They outlive the algorithms that generate, retrieve and transform data. Operating systems conuol processes and resources in a perpetual loop, without computing a result like an algorithm. Programming of man-machine interaction does not produce results either, but controls objects like windows, menues, scroll bars, etc., in a dialog with the user. All simulations by computer are mappings of objects of a real or imagined world and their interactions into objects inside the computer. Such applications together with the idea of the encapsulation of data and abstract data types led to the ‘object-oriented imperative thought model. Here the data receive as much attention asthe algorithms and both are combined to a higher concept: the object. Algorithms ‘may be seen as belonging to the data to which they may be applied. Passive data and active algorithms are united to form active objects. A computation in the object- oriented view consists of communication via messages among active objects, resulting in the creation of new objects, the destruction of unused objects, the change of data in existing objects, and the output of information. So object-oriented programming may be defined as, simulating a world of communicating active objects. 108 Rechenberg: Programming Languages as Thought Models ‘This view of programming was pioneered by the language Simula (notably a simulation language!) in the late sixties but did not have its breakthrough until the eighties withthe language Smalltalk. ‘The twist in changing the algorithmic into the ob- jectoriented view is demonstrated best by looking atthe concepts and notation of the terms “abstract data type” and “class”. In module-oriented languages like Modula-2 ‘an abstract data type is implemented in a module. So the ubiquitious stack may be defined in the module DEFINITION MODULE StackHods THEE Stack; (opaque) PROCEDURE NewStack (VAR :Stack) ; PROCEDURE Push(s:Stack; x:INTEGER) ; PROCEDURE Pop(s:Stack; VAR x:INTEGER) ; END StackMod. This notation has two interesting aspects: + ‘The module defines the type Stack but itis not the type Stack. + Each procedure has a parameter s of type Stack. The user of this module can declare several stacks by declaring variables like VAR a,byer Stacks and applying to these variables the procedures NewStack, Push, and Pop. The object-oriented view is different. A class definition like DEFINITION CLASS stack: VAR stack! 1.7 (tthe stack itself, e.g., as array*) creates Push (x:TNTECER) + POP(VAR x:INTEGER) + END Stack. replaces the module definition. Contrary to the module, the class has these characteristics: + The class does not only define the type Stack; itis the type Stack. + The procedures do not have a parameter of type Stack. In object-oriented languages, objects of type Siack may be declared (asin Modula-2) by means of a notation like VAR a,b,c: Stack: but for the application of stacks, a different notation is used, namely: a a.Pop (ne) ce; a Push) This notation expresses the view that an object of class ‘Stack consists of four components: the stack itself and the three procedures. The source text a.Push(x) means: “Send the message Push(2) to the object a”. The object obeys the message and changes itself (i.e, its data) by appending x to its component seack. While the module StackMod with its procedures exists only once, the procedures of class Stack are supposed to exist in every object of class Stack. ‘The concept of active objects is enhanced by two other features: inheritance and dynamic binding. Look, for instance, at a hierarchy of geometric objects (Figure 3), All plain figures with closed shape have some attributes in common (enclosed area, circumference), alt polygons have some attributes in common (number of vertices), and so on. This structure may also be modelled in the class system. One can first define a class plain closed figure having the procedural components area and circumference. Then one can define the classes polygon and circle such that they are descendents of plain closed figure. They inherit the ‘components oftheir ancestor class and add further com- ponents or change components of their own. By means of inheritance it is possible to build a large hierarchy of classes with one class, the most general of all, at the top. This isa very useful concept because it mirrors the way humans organize their view of the world. In object-oriented languages the addition of new classes as refinements to existing ones should not require recompilation of the existing classes. This concept supporis reusability of software components in a much more general way than is possible in ‘conventional languages. Dynamic binding is a direct consequence of inheritance. Let Rectangle be the class of all rectangles and Square the class of all squares. Square is a descendant of Rectangle. The classes have diferent pro- Plain closed] twiangle | [quacritaceral] [polygon Teoseates neat rectangle ‘Sullaterad triangle nda Figure 3. A hierarchy of geometric objects. Rechenberg: Programming Languages as Thought Models 109 cedures to compute the diagonal, which in both classes ‘may have the same name but different parameter types: Diagonal (x:Rectangie) the diagonal of class. Rectangle Diagonal (x:Square) the diagonal of class Square This looks like overloading in Ada. Now suppose two variables 2 Rectangles @: square and the assignment PH Although the types of p and q are different, this assignment should be allowed since a square “is” also a rectangle. But what is the type of p after the assignment? Is it Rectangle or Square? The answer is that p has two types: Rectangle is its static type and Square is its dynamic type. The assignment has ‘dynamically bound the variable p to the type Square. If ‘we now consider the procedure call Diagonal (p) the run time system must select the correct version of procedure Diagonal for execution according to the dynamic type of p. Since this cannot be done at compile time, dynamic binding may be a costly process. Seen from the source level, Diagonal is a poly- ‘morphic procedure and its parameter is of a polymorphic daia ype. ‘There is a variety of object-oriented languages. Most of them are derived from algorithm-oriented languages, notably C+, Object-Pascal and Objective-C, but some of them are genuine object-oriented languages, like Eiffel and Smalltalk. Smalltalk is the object-oriented Janguage par excellence. A Smalltalk program is a sequence of expressions, most of them having the simple syntactic structure x P where x is an object and P is a message. The expression x P means: “send ‘message P to object x and return the answer of object x as the result of the expression”. This is like the procedure call P(x) in algorithm-oriented languages. Each item in a Smalltalk program, even a numerical constant like 4, is an object which is capable of ‘obeying messages. An expression as 4 + a, which looks like an ordinary infix expression, is understood in a completely different way. 4 is the object x and + a is the message P. It is a binary message, consisting of the proper message “add” and the parameter a. The whole expression 4 + a means: “send the message “add a" to the object 4 and return the result, which will be the sum Of the value of the object 4 and the value of the object 4. This unusual meaning of usual constructs first necds to be mastered when getting in touch with a language like Smalltalk. To see a number as an object which ‘obeys messages is a large deviation from traditional thinking habits, and most object-oriented languages do ‘ot go that far. But it is an interesting experiment to try ‘out the object-oriented thought model with its full consequences. We can summarize the object-oriented thought model in terms of its distinguishing features as follows: + Active objects: A program is a collection of active ‘objects which act on each other by messages, *+ Classes: Each object belongs to a certain class (ic, abstract data type). A hierarchical class library contains predefined classes, The programmer can use. it as a treasure of raw material to customize the special classes he needs in his program, + Inheritance: New classes may be derived from ‘existing classes. Derived classes inherit all characteristics from their ancestor class as long as these are not explicitly defined anew. This results in hierarchical ordering of classes. + Dynamic binding: At run time variables may be ‘bound to objects of different classes and a procedure call (message) may be forced to make a choice among several identically named procedures according to the dynamic classes of its parameters. 3. The Functional Thought Model The imperative thought model—be it algorithm- oriented or object-oriented—suffers from the disadvantages of the container model of variables a3 discussed in Section 2. This and the desire to raise the Jevel of abstraction in the description of computations had in the sixties already ted to the idea to express ‘computations as mathematical functions. A mathe- ‘matical function isa timeless and containerless concept for computing unknown values from known values. ‘One may define a function D(e, x) with the purpose of computing the derivative of the expression ¢ with respect to the variable , ie., ded. Ife is an expression which contains only add and multiply operators, then the derivative can be defined as D(x) -=0__ifeis aconstant or a variable sifferent from x Daz) =1 D(el+e2,x) = D(cl, x) + D(e2,2) D(el*e2,x) = el*D(C2, x) + Diel, 22 0 Rechenberg: Programming Languages as Thought Models Note the differences in meaning of this mathematical function and a function in an imperative language: + The variables denote values, not the contents of @ storage area, and once a variable has been “bound” to a value, this binding remains in effect during the ‘whole computation. + There is no time involved, The function definition specifies no sequence of actions but only relations ‘between left and right parts. It is only a specitication of the problem. Mathematical functions may be built up with three mechanisms: + distinction by cases: different arguments may lead to different definitions: + recursion; and + composition: an argument of a function may be ‘another function. ‘The question is whether all algorithms can be expressed aas mathematical functions. The answer is yes, because loops in algorithms may be expressed by recursive function definitions and conditional branching may be expressed by function definitions with distinction of cases. For instance, the algorithm to add the natural numbers from 1 ton som(n Loca. begin for ii return = end Sun may be expressed by the function sum(a) = 0 Sum(n) = Sum(a-i)#n for m>0 Functional languages make heavy use of nested function definition and the ad hoc definition of auxiliary functions. Consider, for example, the function fe) atx 12+ eI) +0 which can be written more computer-like as Poe, a,b, ©) = (@*Ce-1) + 6") +e ‘As ale for computation, this is inefficient because the inner expression x-1 is evaluated twice, To circumvent this, one invents an auxiliary function g(v, a, 6,¢) and defines fi,a,b,c) = (x-1,a,b,0) 80%4,B,0) = (ave byte Here x-1 will be computed only once. Computability theory shows that functional Janguages have the same computational power as imperative languages (i.e., both can define all algorithms). However, functional languages have no concept of state. As compensation they offer two features that imperative languages lack: higher order functions and lazy evaluation of arguments. By these ‘means one can write functional programs which are ‘much shorter than their algorithmic counterpars. Higher order functions are functions with parameters that are function definitions. Although there arc algorithm-oriented languages which allow parameters of type procedure, their use in functional languages is ‘more general, because (1) not only the name but the complete definition of a function may be a parameter and (2) a function may deliver a function definition as its value, which is not possible in imperative languages! Regrettably we must refrain from giving examples, since they require a special notation (A- notation) for function definitions. Lazy evaluation ot “call by need” is a special kind of parameter evaluation. The usual way 10 evaluate actual parameters that are not simple constants or variables is “inside out” or “call by value”: The innermost actual ‘Parameter expressions are evaluated first and evaluated completely (eager evaluation). From Algol 60 “call by name” is also known: an actual parameter expression is evaluated each time and completely when its value is needed in a computation. “Call by need” lies between the two: an actual parameter expression is evaluated once when its value is needed the first time and then only to the extent that is needed. Lazy evaluation permits the handling of circular function definitions ‘which otherwise would Iead to infinite computations. Furthermore, lazy evaluation is a mechanism for the ‘modularization of functional programs. Since examples for the power of lazy evaluation rely on the computation with lists, we are also unable to elaborate more on this very interesting topic. The idea of lazy evaluation—although not new—has only recently attracted special interest. Lisp uses call by value, while ‘new functional languages like Miranda [3] and Hope (5} use call by need. ‘The thought model of functional programming is aplly described by Bird and Wadler in their book Introduction to Functional Programming (3]: Programming ia a functional language consists of, building definitions and using the computer to evaluate expressions. The primary role of the Programmer is to construct a function to solve a Rechenberg: Programming Languages as Thought Models a given problem. This function, which may involve a number of subsidiary functions, is expressed in a notation that obeys normal mathematical principles. ‘The primary role of the computer is to act as an evaluator or calculator: its job is to evaluate ‘expressions and print the results. In this respect, the computer acts much like an ordinary pocket calculator. What distinguishes a functional calculator from the humbler variety is the programmer's ability to make definitions to increase its power of calculation. Expressions which contain occurrences of the names of functions defined by the programmer are evaluated by using the given definitions as simplification (or ‘reduction’) nules for converting expressions to printable form. A characteristic feature of functional programming is that if an expression possesses a well-defined value, then the order in which the computer may carry out the evaluation does not affect the outcome. In other ‘words the meaning of an expression is its value and the task of the computer is simply to obtain it. It follows that expressions in a functional language can be constructed, manipulated and reasoned about, like any other kind of mathematical expression, using more or less familiar algebraic laws. The result is 2 conceptual framework for programming which is at once very simple, very concise, very flexible and very powerful. ‘The value of an expression depends only on the values of its constituent expressions, and these subexpressions may be replaced freely by others possessing the same value. An expression may contain certain ‘names’ which stand for unknown quantities. Such names are usually called ‘variables"; but every mathematician understands that variables do not vary: they always denote the same quantity. The characteristic property of mathematical ‘expressions is called referential transparency. Among other kinds of value an expression may denote arc included: numbers, truth-values, characters, tuples, functions, and lists. ‘This thought model is that of a mathematician, not that of @ computer scientist. The assets of functional programming are at the same time is weaknesses because the idea of ‘state’ and the view of a computation as a simulation are absent. This is not to say that functional programming is completely unable to express state and simulation (this is possible using lazy evaluation), but their incorporation appears to be far fetched, unnatural, and unduly complicated. Consequently the application area of pure functional programming is constrained to problems where unknown values are to be computed from known values. In this area functional programs may be much shorter, safer and more abstract than imperative programs, To widen the application area, itis possible to incorporate imperative components in a functional language, as was done in Lisp. But this is a dubious resort because it lets all the blunder of imperative programming creep in, paralyzing the benefits ofthe functional thought model, 4, The Logical Thought Model Functional programming is not the only way to raise programming to a higher level of abstraction, Relational or logic programming is another one. Since the reader may not be familiar with logic programming, we introduce it with a small example that contains its spirit in a nutshell, 4.1 An Example In the logical thought model the computation of ¢ as a function of a and b is seen asa relation among a,b and ¢ that can be true or false. Take, for instance, the theorem of Pythagoras: P+ Pac? It denotes a relation among a, 6 and ¢ which may be expressed by the logical predicate Pythagoras(a, b,c) If we assume a logic programming system like Prolog that “knows” the theorem of Pythagoras, a user may type on the terminal something like Pythagoras (3, 4,5) and the system will answer ‘Another interchange could be 2+ pythagoras (3, 4,6) false ‘This means the relation acts like the call of a Boolean function procedure. More interesting is the case, where ‘one Parameter is a variable, for instance 2+ pythagoras(3,4,C) cis 2 pythagoras(a, 4,5) aoa 12 Rechenberg: Programming Languages as Thought Models ‘This means if one of the parameters is a variable, the Programming system tries to find a value for it (binds the variable to a value) such that the relation becomes true. A relation may also have two variable parameters asin 2 pythagoras (A, 4,0) Asac=5 ‘Then the system tries to bind both variables to values, ‘such that the relation becomes true. Most interesting is the case in which all three parameters are variables: pythagoras (A,B,C) Aw3; Bea; c= 5; Ase B c= 20; Ass; Bei, c+ 137 A= 9; B= 1276-15 Awe: B= 152 C= 177 Aw 12; B= 16; C= 20 ‘The system, if suitably programmed, will respond with all triples (up to a built-in limit for C) which make the relation pythagoras true, ‘The program for the computation of the relation Pythagoras in Prolog could take this form: pythagoras (%,,2) natle (2,20), natLey, 2), patie (X,Y), equal(xsxeyr,z+z), rule 1 patie(1,N) i= Nel. rule natie (M,N) i= NEL, nate (x,%-2),suce(xX,M) « rule 3 suce (4/8) i> equal (#+1,N) . rule 4 This are four “rules”, all consisting of predicates and the sign “:-", which denotes implication. Rule 1 reads: “The predicate pythagoras(X, Y, Z) is true if there exist values for X,Y, and Z such that all the predicates natle(Z, 20), natle(Y, Z), natle(X, ¥), and equal (X*X+Y*Y, Z*Z) are truc.” Rules 2 and 3 define the predicate natle(M, N) to be tre if Mf is a natural number that is less than or equal to the natural number N, and rule 4 defines the predicate succ(M, N) to be truc if V is, the successor of M. equal is a built-in predicate for testing equality. The built-in limit for C is 20, ‘The program pythagoras exhibits the declarative programming style of Prolog in that the rules define only timeless relations and no instructions which must ‘be carried out sequentially. The problem is only defined and its solution is up to the Prolog programming system, which is usually an interpreter. ‘The Prolog programming system contains a built-in backtrack mechanism that tries to bind all variables in a systematic way to values such that the “goal” of the equal(X“X+T*Y, 22) false X does not exist ¥ does not exist, false Z X docs not exist, false © first result & second result Figure 4. The sequence of bindings during an evaluation of pythagoras(X, YZ). ‘computation (inthis case the predicate pythagoras) will become true. For the relation pythagoras(X, ¥, Z) this yields a time sequence of bindings as shown in Figure 4. ‘At any point in time when all right hand side predicates are true, a binding of variables has been found that makes the goal true, This binding is a solution to the problem. ‘The model underlying this kind of programming is that of predicate logic. There are some relations that are always true (rule 2 of the example). These are the “axioms” of “facts”, They are stored and constitute a “knowledge base”. All other relations are defined by one, (or more “rules of inference” which infer their truth from the truth of other, simpler relations. The single kind of inference:used isthe logical implication: ALAAD A we A Ag B but it is written with the right part first: BA Ady Ane and read: “B if Ay and A... and A,”, ‘This description seems to be fully declarative and it is the duty of the executing programming system to find the appropriate bindings for variables (rom infinitely many possibilities) that make the goal true. This is the essence of the logical thought mode!: The world consists of predicates which may, depending on their constituent terms, be true or false. Each predicate is either a fact (Le., unconditionally true) or its truth value is a consequence of the truth values of other predicates, duc to a rule of inference. Given a set of facts, a set of inference rules, and a goal predicate with variables, a logic program tries to find all bindings of Rechenberg: Programming Languages as Thought Models 413 the variables of the goa! to constants that make the goal predicate true. ‘This thought modet has some similarity with the world of a mathematician who wants to prove a theorem, The facts correspond to axioms. By repeatedly applying inference rules, he must find a way to show that the theorem is a consequence of the axioms, Therefore logic programming has something in common with theorem proving and many authors go so far to say that logic programming is nothing else but theorem proving. But this is true only in a very far fetched sense. This is easily seen if one tries to explain that answering the question for which values of A the predicate pythagoras(A, 4, 5) will be true is the proof of theorem. It is true, however, that in logic programming each problem must be defined as a set of predicates, facts and inference rules. Theoretically this is always possible in the sense that a logic program can be written that simulates an arbitrary Turing machine, In practice it depends on the application area and on the skill of the programmer. There are some application areas for which logic programming seems to be natural, in particular combinatorial scarch problems, but there are a lot of others for which it appears to be very unnatural to define all that we want to achieve by the rules of first- ‘order logic, 4.2. The Logical Variable and Unification Besides built-in backtracking, there is another feature that determines the thought model of logic programming: the behavior of the logical variable. At a first look, a variable in logic programming behaves like a variable in functional programming because it is bound to a value in the course of predicate evaluation and remains bound to that value during the rest of the program, There is no assignment in logic programming and no container for variables. But this simplicity is deceptive for several reasons. The first reason is that the functional variable is always bound to some value, from the start of the program until the end (according to the timeless view of a function), while the logical variable is unbound at program start and becomes bound later during the interpretation of the rules. The second reason is due to backtracking. The logical variable only remains bound to a value as long as no backiracking occurs. Backtracking restitutes a variable to a further binding or to unboundedness. The third reason is the mechanism of parameter passing in Prolog. This is done by unification, which is unique among programming languages. In other Programming languages a parameter is assigned as a ‘Whole from the caller to the callee or vice versa, and for 2 particular parameter the direction remains the same for cach call throughout the run of the program. Prolog ‘generalizes this mechanism in that it attempts to unify rather than assign actual and formal parameters. As long as one of them is an unbound variable and the other is a ‘constant or a bound variable, unification and assignment look alike. But if both are unbound variables, as may ‘occur in two sules like goal) .. goal(x) = then X and Y are unified to one unbound variable in the sense that X and Y are from now on identical, A later binding of X to 10 is also a binding of ¥ wo 10. This is an effect unknown in other languages. It gives rise to concise programs and to some powerful programming tricks, but is sometimes difficult to understand. ‘The main data structure of Prolog is the tree, which is essentially the same structure as the list in Lisp. Trees may consist of a mixture of constants and variables. Consider a basket containing a certain amount of a certain fruit. This may be expressed by the tree Sruitbasket(Fruitkind, Amount). Now we look at the two wees fraitbasket(X, 20) and fruitbasket(apple, Z), which may appear as a formal and actual parameter pair. Here, X and Z shall be unbound variables and 20 and ‘apple shall be constants. The two parameters may be unified by binding X to apple and Z to 20 resulting in the tree fruitbasket(apple, 20). Without going into further detail about unification, we see in the last ‘example that the passing of one parameter that is a tee may cause several bindings, and, moreover, in different directions. So the distinction of input and output Parameters is blurred in logic programming. Again, this gives tise to concise programs and to some powerful Programming tricks, but may be very difficult to ‘understand, To conclude, the logical variable may take on any one of three states: unbounded, + partially bounded (to a variable or toa tree with variables), or + completely bounded, and all tee bindings may be resttuted by backtracking, A logical variable may be interpreted as the set of all possible values of a given universe and unification as. reducing this set toa subset. The tree fruitbasket(X, ¥) denotes all infinitely many fruit baskets. The wee Sruitbasker(X, 20) denotes all fruit baskets with 20 pieces, the tree fruitbasket(apple, Z) denotes all fruit baskets with apples, and the unification of both results in thei intersection, the single fruit basket with a4 Rechenberg: Programming Languages as Thought Models 4.3, Imperative Thinking in Logic Programming The enthusiasts of logic programming claim that it is pure declarative programming, consisting only of defining the problem as a set of logic rates. This not only raises the level of abstraction but also frees the programmer from thinking of a solution at all. He merely states the problem in (rudimentary) first order logic, and the Prolog programming sysiem finds the solution itself, It tums out that this claim cannot be fulfilled in reality and this to a degree that I would dare to call it an ‘out and out lie. Look, for instance, at the rules for the Pythagoras program. If they were indeed purely relational, it should suffice to write the first rule as, pythegoras (K/¥,2) i nat (2), nat(Y), pat (xX), equal Oerxeysy, 282) « with nae(N) expressing the relation “N is a natural number”. Further, the right-hand side should be writable in any other sequence, for instance as pythagoras(X,¥.2) 2 nat (t), equal (X*x+Y*%, 22), nat (X), nac(Z). But neither is possible because the backtrack algorithm of Prolog considers only a finite universe of discourse, proceeds from left to right and tries out the rules from top to bottom as they are written by the programmer. ‘Thus, the programmer has to care for a finite sct of numbers, and for an ordering of nules and elements in right-hand sides such that the backtracking process finds 2 solution and does not go astray. This is the reason for the predicate natle, the artificial upper limit for Z, and the special ordering of the right-hand sides. In other words, in writing a Prolog program, the programmer has to simulate the behavior of the Prolog interpreter in his mind with painful precision. In addition he must use special nonlogical language features, particularly the so- called cut, to control backtracking. This leads to the effect that Prolog programs are short and innocent looking, but often difficult to understand for human readers because the simulation of the Prolog machine in the mind can be very sophisticated, 5. Conclusions ‘The analysis of the various thought models leads to another classification of programming languages different from that given in the introduction (Figure 5). ‘On one hand there are languages whose thought model is based on the computation of unknown values from known values. We might call this the “mathematical view”, the “value-oriented view”, or the “result-oriented view” of programming, Here the computer acts as 2 desk Programming Languages Valueffented Object-riene am Sai Algofihmic Functional Logical Eiffel CH Fortran Lisp Prolog Pascal © Miranda Ade Hope Figure $, Another classification of programming languages. calculator which receives input values and computes output values. The computation is essentially autonomous. The entire input must be present at the start ofthe computation, the computer runs without any interruption from the outside world until it stops, and ‘only when it has stopped do the results show up. This is explicitly the view of the imperative algorithm- coiented thought model, but implicitly also that of the functional and of the logical thought model since the functional and logic programmer must have an interpretation of his programs in mind, and thus he is forced to mentally draw on the imperative model. This, view corresponds to three mathematical models: algorithmic programming — Turing machine functional programming — recursive functions logical programming -—_ predicate logic ‘The essential feature of all of these language families seems to be their autonomous behavior. Each connection to the outer world does not fit into this, ‘model, be this an input, an output or an interrupt during, the computation, Since in reality our computations are ‘not autonomous, all these thought models have to be ‘compromised to be of practical value. They must allow input, output and possibly also interrupts during the ‘computation. ‘On the other hand there is the view of the computer as a simulator of some component of a greater system. ‘The whole system may consist only of the computer and the tiser, or it may consist of a network of several computers, other technical devices, and users. Here the computer is only one agent among others and its main purpose is not to transform values into values but to simulate the behavior of one or several objects. We may call this the “simulation view” or the “object-oriented view". Objects may be created and destroyed, and they have a lifetime during which they usually change their Rechenberg: Programming Languages as Thought Models us behavior. The distinguishing feature of objects in contrast to values is that objects have a state, and the state changes over time. So “state” is an essential feature of the simulation view. ‘Traditional programming is determined by the value- oriented view, but the next decades will increasingly stress the object-oriented view. It should be clear that the object-oriented view is the more general one because an autonomous system is a special case of a network of communicating systems. Everything that can be programmed according to the value-oriented view can also be programmed with the object-oriented view. The inverse is true only in theory, not in practice. ‘The functional and the logical thought model may indeed be weakened to include the concept of state. To be useful, these languages must have input and output statements anyway and then, by recursion, a perpetual process may be simulated. Let a perpetual process be ‘defined by the “automata equations” % = fles2) dint = eC) ‘output equation state equation for the points in time 1, 2, ... and the time sequences ¢1, €2, for input, ay, az, .~ for output, and 21, 22, -.. for states. The behavior of such an automaton may be expressed in a functional language by a construct like perpetual-process(z) = Let e@ = input); output iee,2))+ perpetual-procoss(g(e, 2)) and in Prolog by a construct like perpetual-process(z) :~ Input (£), £(B,2,A), output (A), GIB, 2, Nexe2, perpetual-process (Next) « In the opinion of the author this is a very artificial resort that destroys the thought model of the value- oriented languages. In practice this would be so awkward that itis virtually impossible. Look, for instance, at functional programs that perform a man-machine dialog. Nevertheless, a lot of proposals have been made and experiments have been undertaken to combine the advaniages of the different thought models in “wide spectrum languages”. The results are new languages that support two or cven three thought models (some of them also handle parallelism). But such combinations Of thought models are not the topic of this paper, The distinction of the value-oriented and the object- ‘oriented view and the perception that the value-oriented view is a special case of the more general object- oriented view may lead 10 a further conctusion. If one agrees that the value-oriented view is that of ‘mathematics and the object-oriented view that of computer science, one can conclude that the mathematics of computation is a proper part of ‘computer science (and not the other way around as many mathematicians belicve). And, much more important: the concept of algorithm should not remain the basic concept of computer science any longer since it is value-oriented, One of its central features is that every algorithm stops (otherwise it is not called algorithm but procedure, see, for instance, Aho and Ullman (2)). Furthermore it lacks any notion of communication with an environment, excluding input and output while the algorithm is running. This definition is not suited as a basis of computer science. The concept of algorithm should be replaced with another concept that is based on communicating objects in perpetual processes containing algorithms as a special case. Such a concept seems to be very hard to find. Actors [1] may be a step in the right direction. References 1. Agha GA (1988) Actors: A Model of Concurrent Computation in Distributed Systems, Cambridge, Massachusetts: The MIT Press 2. Aho:AV, Ullman JD (1972) The Theory of Pass ‘Translation, and Compiling. Englewood Cliffs Prentice Halt 3. Bird R, Wadler P (1988) Introduction to Functional Programming. New York: Prentice Hall 4. Cleaveland JC (1986) An Introduction to Data ‘Types. Reading, Massachusetts: Addison-Wesley 3. Field AJ, Harrison PG (1988) Functional Programming. Reading, Massachusetts: Addison- Wesley 6. Ghezai C, Jazayeri M (1987) Programming Language Concepts. New York: Wiley 7. Pratt TW (1984) Programming Languages-Design and Implementation. 2. ed. Englewood Cliffs: Prentice-Hall

You might also like