Encapsulation, Inheritance and Polymorphism

Scam is a fully-featured object-oriented language. What does
that mean exactly? Well, to begin with, a programming language
is considered object-oriented if it has these
three features:

encapsulation

inheritance

polymorphism

Encapsulation in this sense means that a programmer
can bundle data and methods into a single entity.
We've seen that a Scam function can
have local variables and local functions.
So, if we consider local variables (including the formal
parameters) as data and local
functions as methods, we see that Scam can encapsulate
in the object-oriented sense.

Inheritance is the ability to use the data and methods
of one kind of object by another as if they were defined
in the other object to begin with.

Polymorphism means that an object that inherits appears
to be both kinds of object, the kind of object it is
itself and the kind of object from which it inherits.

A notion that simplifies encapsulation in Scam is to use
environments themselves as objects. Since an environment can be thought
of as a table of the variable names currently in scope, along with their
values, and an object can be thought of as a table of instance variables
and method names, along with their values, the association of these two
entities is not unreasonable.

Thus, to create an object, we need only cause a new scope to come into
being. A convenient way to do this is to make a function call. The call
causes a new environment to be created, in which the arguments to the
call are bound to the formal parameters and under which the function body
is evaluated. Our function need only return a pointer to the current
execution environment to create an object. Under such a scenario, we
can view the the function definition as a class definition with the
formal parameters serving as instance variables and locally defined
functions serving as instance methods.

Scam
allows the current execution environment
to be referenced and returned.
Here is an example of object creation in Scam:

The variable this is always bound to the current execution
enironment or scope. Since, in Scam, objects and environments the same,
this can be roughly thought of as a self reference to an object. The
the object can be called as if it were a function as long as the arguments
in the call resolve to field names.
The inspect function prints the unevaluated argument followed by
its evaluation.

Running the above program yields the following output:

((obj 'display)) is a:3, b:4
((obj 'total) 0) is 7

It can be seen from the code and the output that encapsulation via this
method produces objects that can be manipulated in an intuitive manner.

It should be stated that encapsulation is considered merely a device for
holding related data together; whether the capsule is transparent or
not is not considered important for the purposes of this paper. Thus,
in the above example, all components are publicly visible.

Any specification of inheritance semantics must be (relatively)
consistent with the afore-mentioned intuition about inheritance.
With regards to inheritance behavior pragmatics, there seems to be three
forms of inheritance behavior that make up this intuition. Taking the
names given by Bertrand Meyer in "The Many Faces of Inheritance:
A Taxonomy of Taxonomies",
the three are extension,
reification, and variation. In extension inheritance,
the heir simply adds features in addition to the features of the
ancestor; the heir is indistinguishable from the ancestor, modulo the
original features. In reification inheritance, the heir completes,
at least partially, an incompletely specified ancestor. An example of
reification inheritance is the idea of an abstract base class in Java.
In variation inheritance, the heir adds no new features but overrides
some of the ancestor's features. Unlike extension inheritance, the heir
is distinguishable from the ancestor, modulo the original features. The
three inheritance types are not mutually exclusive; as a practical matter,
all three types of inheritance could be exhibited in a single instance
of general inheritance. Any definition of inheritance should capture
the intent of these forms.
As it turns out, it is very easy to implement these three forms
of inheritance in Scam.

Scam uses a novel approach to inheritance. Other languages
processors pass
a pointer to the object in question to all object methods.
This pointer is known as a self-reference. This passing of
a self-reference may be hidden from the programmer or may
be made explicit. In any case, Scam displenses with self-references
and implements inheritance through the manipulation of scope.

In order to provide inheritance by manipulating scope, it must be
possible to both get and set the static scope, at runtime, of objects
and function closures. There are two functions that will help us
perform those tasks. They are
getEnclosingScope and setEnclosingScope and are defined
in the supplemental library, inherit.lib.
While at first
glance it may seem odd to change a static scope at runtime,
these functions translate into getting and setting
the__context pointer of an environment (or closure).

Recall that in extension inheritance, the subclass strictly adds
new features to a superclass and that a subclass object and a
superclass object are indistinguishable, behavior-wise, with regards
to the features provided by the superclass.
Consider two objects, child and parent. The extension
inheritance of child from parent can be implemented with
the following pseudocode:

The call to a immediately finds the child's method. The call to
b results in a search of the child. Failing to find a binding
for b in child, the enclosing scope of child is
searched. Since the enclosing scope of child has been reset to parent, parent is searched for b and a binding is found.
In the final call to c, a binding is not found in either the
child or the parent, so the enclosing scope of parent is searched.
That has been reset to child's enclosing scope. There a binding
is found. So even if the parent object is created in a scope different
from the child, the correct behavior ensues.

For an arbitrarily long inheritance chain, p1 inherits from p2, which inherits from p3 and so on, the most distant ancestor
of the child object receives the child's enclosing scope:

As stated earlier, reification inheritance concerns a subclass fleshing
out a partially completed implementation by the superclass. A consequence
of this finishing aspect is that, unlike extension inheritance, the
superclass must have access to subclass methods. A typical approach
to handling this problem is rather inelegant, passing a reference to
the original object as hidden, or not so hidden,
parameter to all methods. Within method
bodies, method calls are routed through this reference. Inheritance in
Python is done just this way; the object reference is bound to the
first formal parameter in all object methods.

That said, our approach for extension inheritance
does not work for reification inheritance. Suppose a parent method
references a method provided by the child. In Scam,
when a function definition is encountered, a closure is
created and this closure holds a pointer to the definition environment. It
is this pointer that implements static scoping in such interpreters.

For parent methods, then, the enclosing scope is the parent. When the
function body of the method is being evaluated, the reference to the
method supplied by the child goes unresolved, since it is not found in
the parent method. The enclosing scope of the parent method, the parent
itself, is searched next. The reference remains unresolved. Next the
enclosing scope of the parent is searched, which has been reset to the
enclosing scope of the child. Again, the reference goes unresolved (or
resolved by happenstance should a binding appear in some enclosing scope
of the child).

The solution to this problem is to reset the enclosing scopes of the
parent methods to the child. In pseudocode:

setEnclosingScope(parent,getEnclosingScope(child));
setEnclosingScope(child,parent);
for each method m of parent
setEnclosingScope(m,child)

As can be seen, the reference to a in the function ba is
resolved correctly, due to the resetting of ba's enclosing scope
by child.

For longer inheritance chains, the pseudocode of the previous subsection
is modified accordingly:

setEnclosingScope(pN,getEnclosingScope(p1))
setEnclosingScope(p1,p2);
for each method m of p2: setEnclosingScope(m,p1)
setEnclosingScope(p2,p3);
for each method m of p3: setEnclosingScope(m,p1)
...
setEnclosingScope(pN-1,pN)
for each method m of pN: setEnclosingScope(m,p1)

All ancestors of the child has the enclosing scopes of their methods reset.

Variation inheritance captures the idea of a subclass overriding a
superclass method. If functions are naturally virtual (as in Java),
then the overriding function is always called preferentially over the
overridden function.

then the new version of b overrides the parent version. The output
now becomes:

((obj 'ba)) is jumpingjacks

This demonstrates that both reification and variation inheritance can be
implemented using the same mechanism. Another benefit is that instance
variables and instance methods are treated uniformly. Unlike virtual
functions in Java and C++, instance variables in those languages shadow
superclass instance variables of the same name, but only for subclass
methods. For superclass methods, the superclass version of the instance
variable is visible, while the subclass version is shadowed. With this
approach, both instance variables and instance methods are virtual,
eliminating the potential error of shadowing a superclass instance
variable. Here is an example:

Moreover, the task of resetting the enclosing scopes of the parties
involved can be automated. Scam provides a library,
named inherit.lib, written in Scam that
provides a number of inheritance mechanisms. The first (and simplest)
is ad-hoc inheritance. Suppose we have objects a, b, andc
and we wish
a to inherit from b and c
(and if both b and c provide functionality,
we prefer b's implementation).
To do so, we call the mixin function:

The other type of inheritance emulates the extends operation in
Java. For this type of inheritance, the convention is that an object
must declare a parent. In the constructor for an object, the parent
instance variable is set to the parent object, usually obtained via the
parent constructor. Here is an example:

The behavior of the inheritance scheme implemented in this paper differs
from the inheritance schemes of the major industrial-strength languages
in one important way. In Java, for example, if a superclass method
references a variable defined in an outer scope (this can happen
with nested classes), those references are resolved the same way,
whether or not an object of that class was instantiated as a
stand-alone object or as part of an instantiation of a subclass object.
This is remeniscent of the inheritance theory of Jean-Baptiste Lamarck,
who postulated that the environment influences inheritance. In Java,
the superclass retains traces of its environment which can influence
the behavior of a subclass object.

With selfless inheritance, the static scopes of the superclass objects
are replaced with the static scope of the subclass object, a purely
Darwinian outcome. The superclass objects contributes the methods
and instance variables (say, the genes of the superclass) but none of
the environmental influences. Thus, the subclass object must provide
bindings for the non-local references either through its own definitions
or in its definition environment.

Polymorphism is a word that literally means "having multiple shapes".
With regards to object-orientation, polymorphism means one kind of object
can look like another kind of object. One concrete example of this
involves inheritance: if object child inherits from object parent,
does the child look like a parent object
as well as a child object? In
other words, can a variable that points to a parent object also
point to a child object? This question is of critical importance
for statically typed languages such as C++ and Java, but is not
so important for dynamically-typed languages like Scam.
This is because a Scam variable can point to any type of entity,
so the question of whether a variable can point to either a child
or a parent is moot.

That said, it is often useful in an dynamically-typed, object-oriented
language to ask whether or not a variable points to an object that
looks like some other object. The is? function, introduced in the
previous chapter, can answer these questions. Consider this set
of constructors: