[comp.lang.smalltalk] addition

budd@orstcs.cs.ORST.EDU (08/20/87)

Let me put in my ha'pennys worth here (inflation may have raised its value
to two cents by now, but certainly no more than that).

In response to the adding addition to points question, almost everybody
pointed to Ingalls article on double polymorphism.  Johnson even said

> ...  You can do this with all the number classes and
> the arithmetic operators.  It results in a nicer system than the one in
> Smalltalk-80 based on "coercion", especially if you want to have matrices,
> polynomials, functions, etc. as part of your arithmetic.

Playing the devil's advocate here, let me disagree.  To see why, imagine
the following thought experient.  Suppose we initially had only two kinds
of numbers, say Integers and Floats, organized using this scheme.  Now you
want to add a third kind, say Fractions.  To implement addition for
fractions, you need the message + in class Fraction, which merely invokes
the message addFraction: in class fraction, but now you need to implement
addFraction: in Integer as well as Float (as well as implementing
addInteger: and addFloat: in class Fraction).  To be precise, every class
that is able to be added to other quantities must know about ALL other such
classes, EXPLICITLY.  If we have N such objects, this requires N**2
methods.

Lets keep a table of the new methods we have added
class	Integer		Fraction	Float
	addFract:	+		addFract:
			addFract:
			addInteger:
			addFloat:

Now consider the technique based on coercion and generality.  To implement
Fraction, we assign it a generaly number, presumably between Integer and
Fraction.  If we try to add an Integer to a fraction, since the generality
of Fraction is higher, it will be passed the Coerce message.  In this case,
coersion is easy and the method in class Fraction can merely make a new
fraction out of the integer.  Similarly, in we try to add a fraction to a
float, the coersion message will go to class Float, which will pass asFloat
to the fraction; again, we need only implment this message in class
Fraction.  So we can add Fractions without adding ANY new methods to the
other classes.

class	Integer		Fraction	Float
			+
			coerce:
			asFloat
			generality

Now admittedly, this works only because I assumed a rule for making
something with less generality into a fraction in class Fraction (namely,
put it over 1 in a new Fraction).  If I didn't want to make this
assumption, I could have added the message asFraction to class Integer (and
any other less general classes).  But in many cases of interest (making a
non-complex into a complex, making a number into a polynomial) such simple
rules are exactly what you want; and these can be added to this scheme with
no more difficulty than we encountered when we added Fractions.

For example, let us continue our thought experiment by adding the ability
to add <number>+<point>.  In the double polymorphism scheme, we need to
add the messages addInteger:, addFraction:, addFloat: and whatever other
types of numbers we have, to class Point.  In addition, we need to add the
message addPoint: to all the exisiting addable classes.  On the other hand,
in the coersion scheme, we need only assign Points a generality value higher
than any number, and add the message coerce: to class Point, which returns
a point with the number in both positions.

(There is a slight bit more work involved here, unless we want to make Point
a subclass of Number, since we have to insure that the proper coersions get
called for).

So it would seem to me that in this particular case, the
generality/coersion scheme uses far fewer methods and is easier to
generalize than the double polymorphism technique.

Now, to hop over the fence, I too always feel a bit a guilt whenever I
explictly test the class of an object.  Thus I always use isKindOf:, rather
than isMemberOf:, so I at least can subclass without any problem.  But, as
Josh Susser at parkplace said:
>  ...  Who really cares what
>  class an object is as long as it behaves correctly anyway?
and clearly testing explicitly the class of an object violates this
principle.  In this particular case, however, the alternative seems worse.

--tim budd	budd@cs.orst.edu, {hp-pcd,tektronix}!orstcs!budd

p.s. Version two of Little Smalltalk should be posted to comp.src.unix
in a few days.  Look for it.

hmm@laura.UUCP (Martin Mosner) (08/23/87)

In article <245100011@orstcs> budd@orstcs.cs.ORST.EDU writes:
>
>Let me put in my ha'pennys worth here (inflation may have raised its value
>to two cents by now, but certainly no more than that).
>
... a lot of arguments why coercion is a Good Thing.
>
>So it would seem to me that in this particular case, the
>generality/coersion scheme uses far fewer methods and is easier to
>generalize than the double polymorphism technique.

I think that for the numerical classes in Smalltalk, where there is
a clear generality order, this scheme is very useful.  It's also
easily extendable as long as the extensions fit in the linear
generality order.  I have done that for complex numbers (which are
trivial) and for some kind of symbolic formulae which were able to
give a graphical representation of themselves.
There are, however, things which are not so linearly structured.
Sorry, I don't have a good example handy.  The only other set of classes
in smalltalk-80 which can easily be interchanged and would thus be
usable as an example are the Collection subclasses, and in this case
compatability is ensured by providing a small number of common
messages which are understood by every Collection.  The same is (at least
theoretically) true for the DisplayObject subclasses.

Is the following idea completely flawed ?
When designing a set of interchangeable classes, first define a
set of basic common behavior.  Now the interaction between the
instances of different classes can use special messages when they
are available, otherwise they fall back to using the primitive
operations.  Let me give an example:
I want to extend the DisplayObject/DisplayMedium class tree.
Currently, DisplayMedium has only Form as a subclass (this is
from memory, I may be wrong).  Assume for a moment that there
are others, like PostscriptPage and VectorDisplay and what else.
All subclasses of DisplayMedium should provide methods for
dsplayin solid rectangles and lines (this is very close to
reality).  However, some DisplayMedia are able to draw circles,
splines and what-have-you.  Now there are some DisplayObjects,
namely Circle & Arc.  These should of course use the special
facilities whenever available.  But they should also be able
to display themselves using only the line drawing operation.

Consider the following method in class Arc:
(Arcs have a radius, start & stop angles and a brush in this example)

displayOn: aDisplayMedium at: centerPoint

    (aDisplayMedium class understands: #drawArcCenter:radius:start:stop:brush:)
	ifTrue: [aDisplayMedium
			drawArcCenter: aPoint
			radius: radius
			start: start
			stop: stop
			brush: brush]
	ifFalse: ["check if aDisplayMedium can draw splines.
		This may result in smoother drawing"
	    (aDisplayMedium class understands: #drawSplinePoints:brush:)
		ifTrue: ["use a spline to draw an approximation"]
		ifFalse: ["draw the arc using drawLineFrom:to:brush"]]

The main point here is that all knowledge about Arc drawing is kept in
the Arc where it belongs.  The special methods in the DisplayMedium
subclass just use special facilities, like the Postscript arc
operator or some hardware arc generator.

This example is realistic, but of course relatively simple.
Question: Is this concept applicable to non-trivial cases as well ?

>
>--tim budd	budd@cs.orst.edu, {hp-pcd,tektronix}!orstcs!budd
>
>p.s. Version two of Little Smalltalk should be posted to comp.src.unix
>in a few days.  Look for it.
I will definitely be looking...

	Hans-Martin Mosner
	...!seismo!unido!hmm, hmm@unido.uucp, hmm@unido.bitnet
D

johnson@uiucdcsp.cs.uiuc.edu (08/24/87)

It is true that double dispatching will take n^2 methods in the worst
case.  However, either these methods will mostly be different, in which
case you would have to have a method somewhere that was checking the
case of the argument and doing the appropriate thing, or they will be
mostly the same, in which case inheritance should greatly reduce the
number of methods.  For example, the addFraction method in Number could
be inherited by both Float and the various Integer classes with no
change.

However, I think that the real problem with double dispatching is a user
interface problem.  We need tools that will let us easily examine a
single method definition in a set of classes.  The "browseImplementersOf"
tool in Smalltalk-80 is not enough, because it only shows the methods
that have already been defined, not the classes that still need definitions.
Given such a tool, it would be trivial to add a large number of simple
methods to a set of classes.

The major problem with generality is that it is not general enough.  I think
that matrices were the class that caused Kurt Heeble to rewrite the number
classes.  I'll try to get him to comment.  There are other complaints I could
make against generality, such as the fact that it is slow and ugly, but those
are mostly a matter of personal opinion.

daleh@tekcae.TEK.COM (Dale Henrichs) (08/25/87)

>
>So it would seem to me that in this particular case, the
>generality/coersion scheme uses far fewer methods and is easier to
>generalize than the double polymorphism technique.
> ...
>In this particular case, however, the alternative seems worse.

It is a moot point to discuss which method is TRULY better, since both 
techniques result in the right answer.  However, I think it is important to 
emphasize the differences between the two methods.

Generality/coersion does use far fewer methods than multiple polymorphism.  
This in itself is not a valid criticism since the number of methods measures 
nothing more than the number of methods.  

Multiple polymorphism is the more GENERAL technique (i.e., allowing more 
flexibility in dealing with exceptions to the rule).

Generality/coersion depends upon type testing, a subtle but important 
distinction. With subclassing, type testing becomes complicated:

	isMemberOf: tests whether the receiver is the same class as the argument and 
excludes subclasses
	isKindOf: tests whether the receiver is the same class or a sublclass of the 
argument

Either test can cause difficulties in getting the correct behaviour.  If I use 
isMemberOf: in my type checking, then I must be explicit in my test cases for 
each class, including subclasses.  If I use isKindOf: I don't have to be 
explicit about subclasses, except when I want to make exceptions to the test 
for subclasses that deviate from the expected behaviour.  

As an example, the test isInteger is used so that Integer, SmallInteger, 
LargePositiveInteger, and LargeNegativeInteger would be treated identically in 
the tests for generality/coercing within Integer itself.  All other Numeric 
classes use isMemberOf:.  Point does not perform the type checking and as a 
result fails under certain circumstances.

Multiple polymorphism eliminates the type checking problem completely, allowing 
each object to act 'correctly', since the necessary type information is encoded 
in the message selector (i.e., addFloat:).  Subclasses override methods where 
appropriate. 

I don't believe that either alternative is particularly bad (although Point 
should have been implemented correctly).  Given a choice, I would rather live 
with an implementation based on multiple polymorphism than generality/coercing, 
since I can't guarantee that I will never need to extend arithmetic beyond the 
limitations of generality/coercing.

Dale Henrichs

susser@parcvax.Xerox.COM (Josh Susser) (08/25/87)

[choke on this, line eater!]

In <245100011@orstcs> budd@cs.orst.edu (Tim Budd) writes:
>But, as Josh Susser at parkplace said:
>>  ...  Who really cares what
>>  class an object is as long as it behaves correctly anyway?

Well, Tim, I'll be the first to admit that I did in fact say just that.
However, I do not work for ParcPlace Systems. I work in the Smalltalk
applications group at Xerox Special Information Systems in Pasadena, CA.
We have been writing Smalltalk applications here since the time of
Smalltalk-76. If I'm going to work at a place nobody knows about, I'm going
to make darn sure everybody knows it! :-)

Back to the discussion at hand: seems to me like the numeric coercion
mechanism is a nice way of dealing with the problem WITHOUT explicitly
testing the class of an object. All you need to do to implement a new Number
subclass is to make sure it responds to the proper protocols and assign it a
generality - nowhere is it necessary to put special case code in other Number
subclasses to handle the coercions. The biggest drawback to this approach is that
it is much slower that using mulitiple polymorphism. I think the generality and
ease of expandability more than makes up for the performance hit. But that's
what I think of Smalltalk in general.

-- Josh Susser
   Xerox Special Information Systems
   Susser.pasa@Xerox.com

You may be a vampire, but you're still my brother.

colin@pdn.UUCP (Colin Kendall) (08/27/87)

In the debate about coercion vs. multiple polymorphism:

Mostly I agree with Dale Henrichs: They both work fine. However, Dale
says that the argument that multiple polymorphism needs more methods
is not a valid criticism. From a practical standpoint, I have to
disagree: methods use OOPs, so more methods use more OOPs. Source
code for methods uses disk space. Also, recent experience in
debugging and modifying Smalltalk code has taught me that the
fewer methods there are, the easier it is to understand.

What I can't understand is the objection that so many people make to
testing an object's class. It seems to be an ungrounded esthetic
objection, and one which was seemingly not shared by the designers, 
inasmuch as there are about 120 such tests in the original Smalltalk-80
image.

In support of their objection, people cite the famous quotation that
says who cares what an object's class is as long as it understands a
message... but how do you know if it understands a message.

Well, perhaps there is a good reason for the objection. I'd like
to know it. I'd also like to know whether there is a similar objection 
to testing whether an object understands a given message.

To make a perhaps depersonalizing metaphor, suppose I know several
(human) languages, and I am dropped into some foreign country and
wish to communicate with the inhabitants. I could say, "I don't
care what nationality you are, as long as you understand Swahili",
and start speaking in Swahili, but my chances of communicating would
be slim. A more logical approach would be to try various messages,
like "How are you", "Comment allez-vous", "Wie geht's", "Como esta",
etc., until I got a response -- if all else failed, I might start
drawing pictures in the dirt.  What I am doing is saying:

(UnknownPerson respondsTo: #howAreYou) ifTrue: [ ...

Note that I am not explicity testing the person's class (nationality);
if he is a Russian who happens to understand French, I am just as happy.

This is similar to what Martin Mosner is proposing in his example
where he tests if an object understands a sophisticated message,
sends it if so, but if not tests for understanding of a different
(slightly less sophisticated) message, etc. In his example of
an Arc trying to draw itself on a DisplayMedium, if the
DisplayMedium is sophisticated enough to know how to help, it
is asked to do so; else the Arc resorts to more primitive methods.

Testing understanding allows the application of *limited* multiple
polymorphism with or without intermixed coercion. To go back to the example that started all this,
Point>+ could be written:

+ delta

( delta respondsTo: #pointAdd: ) ifTrue: [^ delta pointAdd: self].
( delta respondsTo: #asPoint ) ifTrue: [^ "(same as original code)" ].
( delta respondsTo: #+ ) ifTrue: [ ^ x + delta @ (y + delta) ]

In a system where nothing responded to #pointAdd:, this code would
work; for efficiency, as time and space permitted, the method pointAdd:
could be added to various classes.

Thus:
Multiple polymorphism works, but requires a lot of methods.
Coercion works, but only for things which can be ordered by generality.
Testing understanding works with neither disadvantage.

-- 
Colin Kendall				Paradyne Corporation
{gatech,akgua}!usfvax2!pdn!colin	Mail stop LF-207
Phone: (813) 530-8697			8550 Ulmerton Road, PO Box 2826
					Largo, FL  33294-2826

scaletti@uxc.cso.uiuc.edu (08/28/87)

I needed double dispatching for implementing matrix
arithmetic.  While it is easy to use the coercion facility for
standard numeric values, matrix arithmetic has two kinds of
multiplication and an unusual addition.  That is, you can have
either scalar or matrix multiplication, which could possibly be
taken care of with coercion (coerce a scalar to a properly scaled
identity matrix of the right size, assuming you know which side of
the matrix the scalar is going to multiply). Also, addition is only allowed 
between matrices of the same size.  There would be no error 
checking, unless additional checks are placed in the 
retry:coercing: methods.

In addition, double dispatching is much faster than the retry:coercing:
mechanism, the resulting code is much cleaner (no class checks),
and the code can also be verified to be type safe.

The only drawback is the order n^2 number of methods.

			Kurt Hebel

daleh@tekcae.TEK.COM (Dale Henrichs) (08/29/87)

>From a practical standpoint, I have to
>disagree: methods use OOPs, so more methods use more OOPs. Source
>code for methods uses disk space. 

I will stand by my statement that method count comparisons are not valid.  On 
some systems, OOPS are a limited resource and we all must deal with the limits 
of disk space and real memory.  In practical terms, I can reduce my method 
count while still increasing my usage of OOPS and disk space.  Therefore, 
counting methods will not help me solve these problems. 
 
> Also, recent experience in
>debugging and modifying Smalltalk code has taught me that the
>fewer methods there are, the easier it is to understand.

As for understandability, I am not so sure that the coercing/generality code in 
Smalltalk is the model of "understandable code".  I agree that in trying to 
understand a specific function, it is easier when there are fewer methods to 
look at.  However, method count is not really the operative factor in 
understanding the code.

Well structured applications (those that distribute responsibility across a set 
of 'logical' classes and implement a 'single function' per method) ARE easier 
to understand and more importantly easier to subclass and use in my own 
applications.

Given two well structured applications and given that ALL other things were 
equal, I too would prefer the one with fewer methods.
	
>
>What I can't understand is the objection that so many people make to
>testing an object's class. 
> ...
>Well, perhaps there is a good reason for the objection. I'd like
>to know it. 

Given an isolated application with a specific goal, there is no valid objection 
to explicitly testing an object's class.  However, one of the major advantages 
of using Object-Oriented languages is that once I've implemented some behaviour 
in a class, I have the opportunity to use that behaviour in ANOTHER 
application, either by using the class as is, or subclassing it and modifying 
the behaviour slightly.  Explicit type checking reduces the utility of a class, 
since I may have to 'unnecessarily' subclass in order to override the type 
checking...  

>Thus:
>Multiple polymorphism works, but requires a lot of methods.
>Coercion works, but only for things which can be ordered by generality.
>Testing understanding works with neither disadvantage.

"Multiple polymorphism" works, and is a general approach to a difficult 
problem.
Coercion works in this specific case, but has limitations as a general 
technique.
Testing understanding works, but seems to be redundant.  

If you are writing an application (such as an arithmetic package),  message 
testing is unnecessary, since you SHOULD implement the capability in a 
consistent manner across all classes involved, in which case, all numeric 
objects would either respond to asPoint OR  pointAdd:.  It isn't necessary to 
handle both cases.  One of the nice things about 
Object-Oriented languages is that this type of checking isn't NECESSARY.  The 
system will tell you that asPoint is not understood, in which case a mistake 
has been made; either asPoint was not implemented where it should have been, or 
you have just tried to add 1@1 to a Symbol.

Dale Henrichs

colin@pdn.UUCP (09/02/87)

In article <1041@tekcae.TEK.COM>, daleh@tekcae.TEK.COM (Dale Henrichs) writes:
>Given an isolated application with a specific goal, there is no valid objection
>to explicitly testing an object's class.  However, one of the major advantages 
>of using Object-Oriented languages is that once I've implemented some behaviour
>in a class, I have the opportunity to use that behaviour in ANOTHER 
>application, either by using the class as is, or subclassing it and modifying 
>the behaviour slightly.  Explicit type checking reduces the utility of a class,
>since I may have to 'unnecessarily' subclass in order to override the type 
>checking...  

You may have to, but you may not. It seems equally likely that if you
have a behaviour which includes the message: self printString, you may
have to 'unnecessarily' subclass in order to override the self-printing,
if the printing behavior is not desired in the other application.
The logical extension of this is that any action reduces the utility of
a class, since overrides may be needed by related applications; so the
ideal method would do nothing.

>If you are writing an application (such as an arithmetic package),  message 
>testing is unnecessary, since you SHOULD implement the capability in a 
>consistent manner across all classes involved, in which case, all numeric 
>objects would either respond to asPoint OR  pointAdd:.  It isn't necessary to 
>handle both casks.  One of the nice things about 
>Object-Oriented languages is that this type of checking isn't NECESSARY.  The 
>system will tell you that asPoint is not understood, in which case a mistake 
>has been made; either asPoint was not implemented where it should have been, or
>you have just tried to add 1@1 to a Symbol.

Agreed. The point>+ method I described was intended to portray the
methodology only. I would not actually use it in this case, where, as
Dale says, if the argument doesn't understand asPoint a mistake has been
made. I use it only when the arguments are not expected in advance to
be of any particular class, which happens a lot.
One of the nice things about 
Object-Oriented languages is that it allows you to do this type of checking,
and take appropriate action based on the results. 







-- 
Colin Kendall				Paradyne Corporation
{gatech,akgua}!usfvax2!pdn!colin	Mail stop LF-207
Phone: (813) 530-8697			8550 Ulmerton Road, PO Box 2826
					Largo, FL  33294-2826

franka@mmintl.UUCP (Frank Adams) (09/08/87)

In article <1164@pdn.UUCP> colin@pdn.UUCP (Colin Kendall) writes:
>What I can't understand is the objection that so many people make to
>testing an object's class. It seems to be an ungrounded esthetic
>objection, and one which was seemingly not shared by the designers, 
>inasmuch as there are about 120 such tests in the original Smalltalk-80
>image.

I think the objection is for the most part more recent than the original
Smalltalk-80 image; certainly more recent than much of the work thereon.
People noticed that these class checks caused problems, by reducing later
polymorphism.  The theory of OOP developed further, and they don't fit
according to most versions of it.  (Of course, with true multiple
inheritance, checking for "isKindOf:" becomes more acceptable.)

>Well, perhaps there is a good reason for the objection. I'd like
>to know it. I'd also like to know whether there is a similar objection 
>to testing whether an object understands a given message.

Certainly not as strong an objection.  But there is one problem in
Smalltalk: the method which implements the message may only generate an
error.  In this case, the object really doesn't "respond to" the message,
but "respondsTo:" will return "true".

>This is similar to what Martin Mosner is proposing in his example
>where he tests if an object understands a sophisticated message,
>sends it if so, but if not tests for understanding of a different
>(slightly less sophisticated) message, etc. In his example of
>an Arc trying to draw itself on a DisplayMedium, if the
>DisplayMedium is sophisticated enough to know how to help, it
>is asked to do so; else the Arc resorts to more primitive methods.

An alternative is make an unconditional message send in the Arc method, and
put the "more primitive methods" in the default implementation in
DisplayMedium.  This is the multiple polymorphism solution, and it seems to
me to be better in this case.
--------------------------------------------------------------
The approach I currently favor for arithmetic is to use multiple
polymorphism, but to try to limit the number of methods it is used for.
Thus, I use multiple polymorphism for addition, but not for subtraction.
Instead, I have the method:

(Number) - aNumber
    ^self + aNumber negated

For speed, I also implement "-" at other levels, with a type check:

(Complex) - aNumber
    ^aNumber class == Complex
      ifTrue: [real - aNumber real i: imaginary - aNumber imaginary]
      ifFalse: [aNumber negated plusComplex: self]

The last block could be just "[super - aNumber]", but this technique is
faster.  "plusComplex" is a secondary method for "+":

(Complex) + aNumber
    ^aNumber plusComplex: self

I am not entirely happy with this approach, but it represents a reasonable
compromise between speed and space considerations.
-----------
An issue I have not resolved is typified by the following:

I have a class "Modulus" with components "residue" and "modulus".
Arithmetic can be performed on two Moduli with the same modulus; this is
standard modular arithmetic.  Likewise, one can multiply a Modulus by a
scalar constant, etc.

I also have a class "Polynomial", which has an indexed instance variable
with the coefficients of a Polynomial.  Again, one perform arithmetic on
Polynomials, or with a Polynomial and a scalar.

The problem is, when I multiply a Modulus by a Polynomial, how do I decide
which is the scalar?  The Modulus could have residue and modulus both of
class Polynomial, so that I should multiply the residue by the Polynomial,
and reduce the result mod the modulus.  Alternatively, the Polynomial could
have coefficients which are Moduli, with the same modulus as the Modulus, in
which case I should multiply each coefficient by the modulus.  (Of course,
the multiplication could also be illegal.)  How can one set things up so
that it is quickly and easily decided which method is to be used?
-- 

Frank Adams                           ihnp4!philabs!pwa-b!mmintl!franka
Ashton-Tate          52 Oakland Ave North         E. Hartford, CT 06108