2.1 The message-passing sub-paradigm 2.2 The distributed-function-definition sub-paradigm3 An example using the POP-11 objectclass library.
3.1 Defining person records 3.2 Defining a method to print a person object. 3.3 Creating student as a sub-class of person 3.4 Multiple dispatch in lib objectclass
It is fairly easy to say what the functional and logic paradigms are about:
It is somewhat harder to characterise the imperative paradigm.
The object oriented paradigm is about (a) creating "objects" in which behaviour is associated with data and (b) ways in which these objects can be placed in a hierarchy in which one kind of object can inherit the behaviour of others and (c) ways in which the structure of objects can be encapsulated so as to allow only legitimate enquiries to be made about them.
Now (a) and (c) are both properties of closures - a closure is an association of behaviour (i.e. some code) with data, and the data can only be accessed only by passing appropriate arguments to the closure. So it is not surprising that we will use closures to represent objects in our illustration of object-orientation.
We often speak of the piece of code that implements a particular behaviour for a class of objects as a method. We have already met the concept of method when we were implementing the special forms of the eval_env and eval_eager functions. So, we could regard that evaluator as running partly on object-oriented lines. In that view, all Scheme expressions with a given special form form a class sharing the same evaluation method.
For example, the if special form has the evaluation method method_if. Thus when we evaluate:
(eval_env '(if (= x 2) 3 4) the-global-environment)
then method_if is called actually to perform the evaluation, via the method-lookup in alist_method.
In general the essential aspect of object orientation is that a named action applied to an object is mapped on to a piece of code for effecting that action by means of a flexible mapping which takes into account both the name of the action and the class of object to which it is applied.
When we did the Scheme expression evaluator we mapped from object to action by means of an explicit association list alist_method. In an object oriented language such as Smalltalk, C++ or Java, this mapping is done in a way that is opaque to the user - that is it is hidden in the implementation.
There are two sub-paradigms within object orientation:
In this sub-paradigm an object is "sent" a "message" to cause it to exhibit behaviour. For example, if we were simulating a university, we might have person objects, each one of which could be told to print himself or herself. This could be accomplished, with a Scheme implementation of this sub-paradigm, by:
(send person 'print)
which might cause the output:
name: Frederika Forsythe age: 42 sex: female
Likewise
(send person 'age)
might return the result 42.
Sometimes arguments are embodied in the message, for example
(send person 'location 234 456)
might be used to reposition a person in a simulation that went into topographic detail.
The most significant development of this paradigm was associated with the Smalltalk language.
Within this paradigm, a general concept of inheritance corresponds to the possibility that one class of objects may share some of the attributes of another class. For example, in our university model we would have students who are a kind of person and professors who are a kind of person. Students will be able to handle all the messages that a person can handle - they have ages, sexes, addresses etc.. However they also have attributes peculiar to students, such as the courses that they take. Moreover they may respond differently to standard messages.
(send student 'print) name: Jeremiah Jolt age: 23 sex: male course: CMPSCI 287, STATS 101
and
(send student 'courses) ==> '( (CMPSCI 287) (STATS 101))
The "send a message" approach has some virtues. It is a quite natural way of talking about a distributed system in which the objects are not all resident on a single computer so that information has to be transferred via some kind of communication link, for example a local area network.
It also has some snags, not least among which is the fact that the nice higher-order functions we have written in this course are not so readily applicable to this paradigm. For example if we wanted to convert a list of students into a list of lists of the courses they were taking we could not use map in quite such a simple way. In the functional paradigm, if courses was a function mapping from students to their courses, we would write
(map courses students)
whereas in the message-sending sub-paradigm we have to write: (map (lambda (s) (send s 'courses)) students)
Moreover the fact that a message has to be addressed to one object makes it tricky to implement certain functions which naturally involve two objects, such as comparing them for equality.
One common term for this limitation is that this approach embodies single dispatch, that is methods are selected on the basis of a single object (and are usually associated fairly directly with that object).
One can regard the C++ language as supporting a single-dispatch approach to method invocation.
The Common Lisp Object System (CLOS) and the objectclass library of the POP-11 language support a different sub-paradigm, one in which object orientation is effected by extending the definition of functions in an incremental way. In Scheme syntax, one would say:
(print person)
and have the same information printed out
name: Frederika Forsythe age: 42 sex: female
However, instead of the print function being defined in one place
(define (print person) (print-basic-info person) (cond ((student? person) (print-student-info person)) ((professor? person) (print-prof-info person)) ) )
the definition of how to print a person is distributed among various method definitions, using a new special form.
By way of a change we will consider the objectclass library for POP-11, written by Steven Knight of Hewlett Packard Laboratories, Bristol. POP-11 is derived from POP-2 [Burstall & Popplestone 1968], and is in essence a form of LISP with ALGOL-60 derived syntax. For example the factorial function in POP-11 is defined as:
define fact(n); if n=0 then 1 else n*fact(n-1) endif enddefine;
To enter POP-11 from Scheme, execute the function call (pop11) in your base window. To get back, type "Scheme". There is no literate version of POP-11, so you'd have to comment out the English (with the /*...*/ comment brackets familiar to C users.
In POP-11, to print an expression expr you do
expr =>
For example
fact(5) =>
will print out
** 120
Finally, to declare a new global variable, and give it a value, one uses vars command, exemplified below:
vars x = fact(120);
creates (if required) a new variable x and makes it have fact(120) as its value.
To load the objectclass library we need to execute the POP-11 command: lib objectclass;
This will take some tens of seconds, during which the message: ;;; Objectclass, version 5.02 ;;; Loading source files. ;;; Done.
is printed out. We are now ready to define our basic person class of objects. This is done with the following syntax:
define :objectclass person name = 'John Doe'; age :int = 25; ;;; The type-constraint is optional. sex = 'male'; enddefine;
This has the effect that each member of the person class is a record with three fields for name, age and sex. These fields are accessible with three functions name, age and sex which are created (or extended) by the objectclass declaration.
The declaration also creates two functions (called newperson and consperson) for creating person objects and one function (destperson) for taking them apart.
We don't need to consider the effect of the destperson function.
Thus, we can create a new person object, and bind the variable p1 to have it as its value by:
vars p1 = newperson();
and print out the new person, using a default print-method that is created when the person objectclass is created:
p1=>
**
If we don't like the way the default print function works, we can define a function print for a person by defining the method:
define :method print(p:person); printf('name: %p \n', cons(name(p),[])); printf('age: %p \n', cons(age(p),[])); printf('sex: %p \n', cons(sex(p),[])); enddefine;
Here printf is a system function which loosely follows the convention of the printf function of the C language, that is to say, the first argument specifies a string with "slots" %p which will be filled in by values taken from the list which is the second argument. The empty list is denoted by [] in POP-11, and cons is a POP-11 function corresponding to the Scheme cons function.
Now if we say:
print(p1);
we obtain the output:
name: John Doe age: 25 sex: male
We can create another person:
vars p2 = consperson('Frederika Forsythe',42,'female' );
so that evaluating:
print(p2)produces the output:
name: Frederika Forsythe age: 42 sex: female
define :objectclass student isa person; classes = ['CMPSCI 287' 'MATH 183']; enddefine;Here the force of the isa construct is to say that the new student class of objects ________inherits all the attributes of a person (name, age and sex) and by default inherits the _______methods that apply to a person. To be precise, the records that implement the student object have fields for name, age, sex, with methods of those same names. However, a student object has a new attribute, namely the classes attribute. vars s1 = newstudent(); If we print s1 using the built-in generic printing function of POP-11, we see that students have inherited the attributes of people, but have classes as well: s1=> **
define:method print(s:student); printf('classes: %p \n', cons(classes(s),[])); enddefine;However if we call this, we simply get the information about classes:
print(s1);gives:
classes: [CMPSCI 287 MATH 183]We could of course print out the people-attributes of a student, by copying our earlier code for printing persons, but more conveniently there is a special form defined in lib objectclass, namely call_next_method, which will, in this case, call the method for the person objectclass.
define:method print(s:student); call_next_method(s); printf('classes: %p \n', cons(classes(s),[])); enddefine;Now we find that:
print(s1);will produce:
name: John Doe age: 25 sex: male classes: [CMPSCI 287 MATH 183]How does call_next_method work? Well, the isa construct defines an ______object _________hierarchy in which the student class comes immediately below the person class. The expression call_next_method(s), knowing that s belongs to the student class of objects, arranges for the method appropriate to the next higher class in the hierarchy, that is the person class, to be called.
This approach supports multiple dispatch, that is to say one can qualify some or all arguments of a method with class membership constraints. For example, one might define one person as being senior to another in terms of age:
define :method senior(p1:person, p2:person); age(p1) > age(p2) enddefine;Whereas one might define one student as being senior to another in terms of the number of classes taken:
define :method senior(s1:student, s2:student); length(classes(s1)) > length(classes(s2)) enddefine;What happens if we try to compare a student with a non-student. The system searches the class-hierarchy to find a method that applies to both. In this case the definition of senior for two people is used. Where a class-hierarchy has some complexity it is possible that more than one method may exist which is applicable to the arguments of a multiply-dispatched method call. What is to happen in this case? In fact lib objectclass uses a simple lexicographical rule to make the choice. The Common Lisp Object System does this by default, although this default is user-modifiable. The Java language, which does this kind of search at compile-time, insists on finding a unique _________maximally ________specific method-descriptor [Gosling, Joy & Steele 1996]. __________References Burstall, R.M. and Popplestone, R.J., [1968] The POP-2 Reference Manual, Machine Intelligence 2, pp. 205-46, eds Dale,E. and Michie,D. Oliver and Boyd, Edinburgh, Scotland. Gosling,J. Joy,B. and Steele,G.[1996] The Java Language Specification. Addison Wesley.