Instructor's Guide
intro,
paradigms,
comparison,
design,
prototypes,
architectures,
summary,
Q/A,
literature
Object-oriented programming has evolved as a new and
strong paradigm of programming.
Has it?
Of the languages mentioned, only Smalltalk
has what may be considered a radically new language design
(and to some extent also the language Self, that we will
discuss in the next section).
Most of the other languages, including Eiffel, C++
(and for that matter also CLOS and Oberon),
may be considered as object-oriented extensions of already
existing languages or, to put it more broadly, language
paradigms.
Most popular are, evidently, object-oriented extensions
based on procedural language paradigms,
closely followed by the (Lisp-based) extensions of the
functional language paradigm.
Less well-known are extensions based on the logic programming
paradigm, of which DLP is my favorite example.
In [Wegner92], it is argued that the logic programming
paradigm does not fit in with an object-oriented approach.
I strongly disagree with this position.
However, the arguments given in [Wegner92] to defend it
are worthwhile, in that they make explicit what desiderata
we may impose on object-oriented languages.
Remaining within the confines of a classical object model,
the basic ingredients for an object-oriented extension
of any language (paradigm) are:
objects, classes and inheritance.
Although the exact meaning of these notions is
open for discussion,
language designers seem to have no difficulty in
applying these concepts to extend (or design) a programming language.
Open systems
- reactive -- flexible (dynamic) choice of actions
- modular -- (static) scalability
Dimensions of modularity
- encapsulation boundary -- interface to client
- distribution boundary -- visibility from within objects
- concurrency boundary -- threads per object, synchronization
slide: Dimensions of modularity
According to [Wegner92],
the principal argument against combining logic programming
and object-oriented programming is that such a combination
does not support the development of open systems
without compromising the logical nature of logic programming.
Openness may be considered as one of the prime goals of
object orientation.
See slide [5-open].
A software system is said to be open
if its behavior can be easily modified and extended.
[Wegner92] distinguishes between two mechanisms to achieve
openness; dynamically through reactiveness,
and statically through modularity.
Reactiveness allows a program to choose dynamically between
potential actions.
For sequential object-oriented languages, late binding
(that is, the dispatching mechanism underlying virtual function calls)
is one of the mechanisms used to effect
the dynamic selection of alternatives.
Concurrent object-oriented languages usually offer an additional
construct, in the form of a guard
or accept statement, to determine dynamically which method call
to answer.
In both cases, the answer depends upon the nature of the object
and (especially in the latter case) the state of the object
(and its willingness to answer).
Openness through modularity means that a system can safely be extended by adding
(statically) new components.
The issue of openness in the latter sense is immediately related
to the notion of scalability,
that is the degree to which a particular component
can be safely embedded in a larger environment
and extended to include new functionality.
At first sight, classes and inheritance strongly contribute
to achieving such (static) openness.
However, there is more to modularity than the encapsulation
provided by classes only.
From a modeling perspective, encapsulation (as provided
by objects and classes) is the basic mechanism to define the
elements or entities of a model.
The declarative nature of an object-oriented approach
resides exactly in the opportunity to define such entities
and their relations through inheritance.
However, encapsulation (as typically understood in the
context of a classical object model) only provides
protection from illegal access from without.
As such, it is a one-sided boundary.
The other side, the extent to which the outside world
is visible for the object (from within),
may be called the distribution boundary.
Many languages, including Smalltalk and C++,
violate the distribution boundary by allowing
the use of (class-wide) global variables.
(See also section [meta].)
Evidently, this may lead to problems when objects reside on distinct
processors, as may be the case in distributed systems.
Typically, the message passing metaphor
(commonly used to characterize the interaction between objects)
contains the suggestion that objects may be physically
distributed (across a network of processors).
Also (because of the notion of encapsulation),
objects are often regarded as autonomous entities,
that in principle may have independent activity.
However, most of the languages mentioned do not (immediately)
fulfill the additional requirements needed for actual physical
distribution or parallel (multi-threaded) activity.
Object-oriented logic programming
Logic programming is often characterized as relational
programming, since it allows the exhaustive
exploration of a search space defined by logical relations
(for instance, by backtracking as in Prolog).
The advantage of logic programming, from a modeling point of view,
is that it allows us to specify in a logical manner
(that is by logical clauses) the relations between
the entities of a particular domain.
A number of efforts to combine logic programming with
object-oriented features have been undertaken,
among which is the development of the language Vulcan.
Vulcan is based on the Concurrent Prolog language
and relies on a way of implementing objects as
perpetual processes.
Without going into detail, the idea (originally proposed in
[ST83]) is that an object may be implemented as
a process defined by one or more (recursive) clauses.
An object may accept messages in the form of a predicate call.
The state of an object is maintained by parameters of the
predicate, which are (possibly modified by the method call)
passed to the recursive invocation of one of the clauses
defining the object.
To communicate, an object (defined as a process)
waits until a client asks for the execution of a method.
The clauses defining the object are then evaluated to
check which one is appropriate for that particular method call.
If there are multiple candidate clauses, one is selected and evaluated.
The other candidate clauses are discarded.
Since the clauses defining an object are recursive, after the evaluation
of a method the object is ready to accept another message.
The model of (object) interaction supported by Concurrent Prolog
requires fine-grained concurrency, which is possible
due to the side-effect free nature of logical clauses.
However, to restrict the number of processes created during the evaluation of a goal,
Concurrent Prolog enforces a committed choice between
candidate clauses, thus throwing away alternative solutions.
[Wegner92] observes, rightly I think,
that the notion of committed choice is in conflict
with the relational nature of logic programming.
Indeed, Concurrent Prolog absolves logical completeness
in the form of backtracking, to remain within the confines
of the process model adopted.
[Wegner92], however, goes a step further and states
that reactiveness and backtracking are irreconcilable features.
That these features may fruitfully be incorporated in a single
language framework is demonstrated by the language DLP.
However, to support backtracking and objects,
a more elaborate process model is needed than
the process model supported by Concurrent Prolog
(which in a way identifies an object with a process).
With such a model (sketched in appendix E),
there seems to be no reason to be against the marriage
of logic programming and object orientation.