[comp.object] Re-use and functions as data

aarons@syma.sussex.ac.uk (Aaron Sloman) (12/16/90)

>
> (Paul Johnson) writes:
> >....If you have fully analysed your
> >problem and know what routines are to be called where then you can
> >code in Pascal or C just as easily.
>

rick@tetrauk.UUCP (Rick Jones) writes:

> Organization: Tetra Ltd., Maidenhead, UK
  [...portion omitted...]
> ....There are lots of
> ways to produce a structured solution to a _specific_ problem;  object
> orientation is just one way to do it.  But that doesn't make your system more
> resilient to change, which is the real point.
>
> The most important point about reuse is the ability to reuse
    something in a way
> that was not anticipated when it was written.  Rigid hierarchies don't stand
> much chance of letting you do that.
  [...portion omitted...]
>
> In a function oriented system, reuse is provided by function libraries.
        These
> work quite well, but we all know that they are limited in scope because of
> problems of encapsulation and extension.  The acclaimed benefit of object
> oriented methods is better reusability.
  [...portion omitted...]
>
> I feel this concern about MI is all to do with implementation problems of
> languages which have been (short-sightedly) designed without
    due consideration
> for the ultimately essential support of multiple inheritance.

Many languages do have restrictions of this sort. However, some
(though probably not all) of the facilitation of re-use that comes
from OO Languages can also be achieved by languages that allow
procedures to create functions and return them as results that can
be used as input to other procedures that call them, e.g. by
applying them to approriate data. This sort of facility is provided
by Scheme, Common Lisp, Pop-11 (A Lisp-like language with a
Pascal-like syntax that many find more readable), ML, and others.
One requirement for this is either a polymorphic type mechanism
(as in ML) or the ability to postpone type-checking till run time
as in the other languages, which therefore provide greater flexibility.

A toy example, to illustrate: suppose you want to have a re-usable
function -create_pairs- that takes in two lists of objects (of any
type), a function that creates two-element records, with one object
from each list, and then makes and returns a sorted list of the two
element records, where the required ordering of the sorted list
depends on the types of objects in the list, etc. (How would you do
this in an OO Language?). In Pop-11 you could define this as follows


define create_pairs(list1, list2, makepair, testbefore) -> list;
    lvars
        item1, item2, list1, list2,
        procedure makepair,         /* the pair creator */
        procedure testbefore,       /* the sort predicate */
        list;

    /* make the unsorted list and assign it to list */
    [%
        for item1, item2 in list1, list2 do
            makepair(item1, item2)
        endfor
    %] -> list;

    /* sort it, using the the library -syssort- and the supplied predicate */
    syssort(list, testbefore) -> list
enddefine;

For example, given two lists of numbers, create a list of pairs of
numbers, sorted by the sum of the pairs.


vars numbers1 = [12 14 16 18],
     numbers2 = [28 25 22 19];

Assuming the pairs have been created using the system procedure
-conspair- and can therefore be accessed using -front- and -back-, define
the ordering predicate.

define pairbefore(pair1, pair2) -> boolean;
    /* true iff sum of elements in pair <= those in second */

    lvars pair1, pair2, boolean;

    front(pair1) + back(pair1) <= front(pair2) + back(pair2)  -> boolean
enddefine;

/*Test create_pairs and print out the result */

create_pairs(numbers1, numbers2, conspair, pairbefore) =>
** [[18|19] [16|22] [14|25] [12|28]]

(This example does not use the most efficient coding).

Note that this procedure can be applied to lists containing any type
of data, provided that an appropriate pair creating and pair
ordering procedure are available. I.e. The procedure -create_pairs-
is re-usable for many different sorts of data-types, whose existence
need not have been anticipated by the author.

Re-use can be further simplified by using "properties" to associate
with each type of entity, or each pair of types, the appropriate
pair-creating and pair-comparing procedures. Then the create_pairs
procedure would not need to be given the last two procedural
arguments explicitly. It would use the contents of the first two
arguments to get the appropriate procedures from the properties.

A program (or development team) that dynamically created new types
of data, and dynamically created appropriate functions for creating
pairs and ordering them, and stored those functions in the appropriate
properties, could use create_pairs on lists of objects of types that
were not anticipated by the original programmer, in accordance with
Rick's comment:

> The most important point about reuse is the ability to reuse
> something in a way that was not anticipated when it was written.

Here, though, instead of information having to be associated with
each type of object we allow extendable information to be associated
with a particular generic procedure: via the two extendable
properties.

OO languages provide support for this through inheritance mechanisms.
But there are other ways, as I hope my rather simple and artificial
example illustrates. It should be clear how far more sophisticated
examples could use the same technique, e.g. a re-usable network
matching program for networks of various sorts, a re-usable database
manager for storting data of different sorts, etc. This is common
practice among Pop-11 programmers (as the -syssort- library program
used in the example, illustrates, although the present version requires
the user to specify the ordering predicate rather than associating
one ordering predicate with each type of object, which would be rather
restrictive).

(However, because some users prefer to have an explicit OOP approach,
including multiple inheritance, etc., Poplog Pop-11 now also
includes an optional language extension to support it.)

Aaron Sloman,
School of Cognitive and Computing Sciences,
Univ of Sussex, Brighton, BN1 9QH, England
    EMAIL   aarons@cogs.sussex.ac.uk

rick@tetrauk.UUCP (Rick Jones) (12/20/90)

In article <4040@syma.sussex.ac.uk> aarons@syma.sussex.ac.uk (Aaron Sloman) writes:

>[ ... ]  Some
>(though probably not all) of the facilitation of re-use that comes
>from OO Languages can also be achieved by languages that allow
>procedures to create functions and return them as results that can
>be used as input to other procedures that call them [ ...]

>A toy example, to illustrate: suppose you want to have a re-usable
>function -create_pairs- that takes in two lists of objects (of any
>type), a function that creates two-element records, with one object
>from each list, and then makes and returns a sorted list of the two
>element records, where the required ordering of the sorted list
>depends on the types of objects in the list, etc.

>(How would you do this in an OO Language?).

OK, you issued the challenge :-)  In Eiffel you would rely heavily on generic
classes.  First, a class to define a PAIR object (note - I've deliberately
squashed normal Eiffel layout so as not to spread this article out too much):

deferred class PAIR [T, U]
export repeat COMPARABLE, item1, item2
inherit COMPARABLE
	redefine infix "<"
feature
	item1: T; item2: U;
	infix "<" (other: like Current) is deferred end;
end

This defines the abstraction of a pair - i.e. it has two members, item1 &
item2, whose types are generic, and is a COMPARABLE type, where the actual
comparison operation is to be defined in a descendant class.  (COMPARABLE is a
library class which provides all the other comparison operators as derivatives
of "<", so only that one has to receive a definition.)

Now for the sorted list of pairs.  Here we use an existing generic library
class SORTED_LIST to create a deferred (abstract) class containing sorted
pairs:

deferred class SORTED_PAIRS [T, U]
export repeat SORTED_LIST
inherit SORTED_LIST [PAIR [T, U]]
feature
	build (list1: LIST [T], list2: LIST [U]) is
		-- builds pairs into this list from two lists
		-- of relevant items
	do
		from
			list1.start; list2.start;
		until
			list1.off or list2.off
		loop
			-- this inherited routine puts the item at the
			-- correct place
			put (new_pair (list1.item, list2.item));
		end;
	end ;

	-- deferred routine to create new pair objects
	new_pair (p1: T, p2: U): PAIR [T, U] is deferred end;
end

We then create non-deferred classes inheriting from the above to handle a
specific type of pair.  Some actual pair class would be like:

class MY_PAIR
export repeat PAIR
inherit PAIR [INTEGER, STRING]
	define infix "<"
feature
	Create (i: INTEGER, s: STRING) is
	do
		item1 := i; item2 := s;
	end;
	infix "<" (other: like Current) is
	do
		Result := item1 < other.item1;
	end;
end

This is an integer/string pair, where the comparison is done on the integer
value.  The sorted list of these pairs would be:

class SORT_MY_PAIR is
export repeat SORTED_PAIRS
inherit SORTED_PAIRS [INTEGER, STRING]
	define new_pair
feature
	Create (ints: LIST [INTEGER], strs: LIST [STRING]) is
	do
		build (ints, strs);
	end;
	new_pair (i: INTEGER, s: STRING): MY_PAIR is
	do
		Result.Create (i, s);
	end;
end

To create a sort_my_pair object, the call to create it requires the two lists
of appropriate single values as arguments.  Code fragment:

	ints: LIST [INTEGER];
	strs: LIST [STRING];
	mp: SORT_MY_PAIR;

	... the first two lists get filled by some means ...

	mp.Create (ints, strs);

This single call will create, populate, and order the list object "mp".

With the proposed improvements to the methods of object creation planned for
Eiffel, this solution could be even more elegant, in particular avoiding the
need for a specific version of the SORTED_PAIRS class for each different PAIR
class.


What this does illustrate is the power of genericity, especially when combined
with inheritance.  I have found in practice that Eiffel's ability to handle
truly generic classes is as important, if not more important, than inheritance.
I don't think this example could be done anything like as easily in an OOPL
with inheritance but not genericity.

There are many roads to software reuse if you can navigate them properly!

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan