Object: Identity vs. Environment

Richard A. O'Keefe ok at cs.otago.ac.nz
Tue Jun 10 04:03:46 UTC 2003


Joel Shellman <joel at ikestrel.com> continues the thread,
which I am getting tired off.

	> It gets worse.  At present, an #isFoo method can do anything it
	> pleases, as long as it follows a few simple rules:
	> (a) an #isFoo method, wherever placed, should be free of malign
	>     side effects.
	> (b) an #isFoo method should answer true or false.
	> (c) an #isFoo method should never raise an exception that it doesn't
	catch.
	
	Why?  Or rather, if it's there, yes certainly don't throw an
	exception for such a simple thing.  But if we did as you
	suggested down below about moving isWebBrowser to a different
	class, and then someone tried to call it on a different class,
	it would throw an exception.  Is that okay?
	
Wrong.  What I said was that an isFoo method should never raise an
exception that it doesn't catch.  In your example, an exception is
raised because there is NO SUCH METHOD.

	The isEmpty is not a good example: that is a different concept.

Why?

The question at issue is precisely the same for #isNumber and #isEmpty:
"does it make sense to send thus-and-such a message to this object NOW?"

	And notice that
	#isEmpty is on Collection where (as I think anyway) it "belongs".
	
We agree.

	The other example makes some sense, but... why would you want to
	put isBeautifulWoman and isWolf on Object?
	
Nobody ever said you would.  I certainly wouldn't.  There is some class
(perhaps DiscWorldMoralAgent) or cluster of classes that they belong to.

Part of the point that I was making is that precisely the same questions
about the placement and appropriateness of isFoo messages DO arise
lower down in the class hierarchy than Object; there is nothing at all
special about Object in this discussion.

	> If someone were to say "let's clean up Behavior, CompiledMethod,
	> DeepCopier, Environment, ImageSegment, LiteralNode, LiteralVariableNode,
	> PackageInfo, SyntaxMorph, SystemDictionary, TilePadMorph, and VariableNode
	> so that the things they apply #isVariableBinding to aren't arbitrary
	> objects but come from a smaller range of objects so that
	#isVariableBinding
	> can move out of Object", I'd say "great!"
	
	I don't quite follow.  Why bother cleaning it up if you're
	saying that that idiom is appropriate in general?
	
But I am *NOT* saying that.
Not at all!
Very far from it!

What I am saying is much more modest:
    the isFoo idiom isn't always INappropriate
  = the isFoo idiom is SOMETIMES appropriate, sometimes not.

I'm replying to people proposing unbelievable replacements for
#isFoo, apparently on the grounds that #isFoo is always WRONG.

I say that #isFoo is SOMETIMES wrong, SOMETIMES right.

	Well... I'm trying to understand the way to develop in
	smalltalk--best practicies, standard idioms, etc.

	If the standard idiom is to put #isFoo on Object and override it
	in subclasses, that would result in potentially hundreds and
	eventually thousands of #isFoo's in Object.

Well, *obviously* that *isn't* the standard idiom and never has been.
Smalltalk-80 has been around fo 23 years now, and we only have 18
#isFoo messages in Object.

The standard idiom is that if have objects in classes X, Y, Z, ... W
(and their descendants) that you want to answer isFoo, you put some
definition of #isFoo in the nearest common ancestor of X, Y, Z, ... W.

Sometimes the nearest common ancestor "feels" to be a bit too far
away, and with some reluctance you introduce several duplicates of
the default implementation, rather than add to the nearest ancestor.

Sometimes that nearest common ancestor is Object.
Most of the time it isn't.

	That seemed rather odd and undesirable to me.
	However, if they should be avoided, then... we're in agreement,
	I believe.

Well, no.  Because you are back to absolutes.

MOSTLY putting things in Object is to be avoided.
SOMETIMES it is fine, because it prevents worse things elsewhere.

	I'm glad you brought my attention to this because this is kind
	of what I'm trying to figure out.  Here in this single method
	is at least 3 different ways of accomplishing the same concept:
	
	** The #isFoo idiom: **
	 anObject isWebBrowser
	  ifTrue: [anObject jumpToUrl: url. ^ true]

I explained that I didn't think this particular test should ever
come out true.

	** The #respondsTo idiom: **
	  ifFalse: [((anObject respondsTo: #model) and: [anObject model
	isWebBrowser])
	    ifTrue: [anObject model jumpToUrl: url. ^ true]].
	
This is mainly an instance of the #isFoo pattern.

Let's look at implementors of #model.  They are
    CRAddFeatureMorph>>model    ^model
        "Part of the Genie UI."
    Controller>>model ^model
	"Part of the old MVC stuff; not relevant here."
    MorphicModel>>model  ^model
        "There is an #isMorphicModel method in Morph,
         overridden by MorphicModel to answer true.
         It's a little confusing that a MorphicModel *has*
         a model, but as we see in PasteUpMorph, it *is* a
         model as well.
    PasteUpMoprh>>model self createCustomModel. ^model
        "makes a MorphicModel if model was previously nil."
    PluggableCollectionMorph>>model ^model
    View>>model ^model
	"Part of the old MVC stuff; not relevant here."

I have disliked and avoided #respondsTo: ever since I realised that
it never told me what I really wanted to know.

The question that (anObject respondsTo: aSelector) answers is
    "Does anObject's class implement or inherit aSelector?"
The question that I'm always interested in is
    "Are there any cases where sending aSelector to anObject
     would not result in some kind of exception?"

Here's a classic example of what I mean.

    anInterval := 1 to: 20.
    anInterval respondsTo: #add:

This answers true.  But the "implementation" is in fact
"self shouldNotImplement"; there is _no_ object for which

    anInterval add: object

will ever do anything but raise an exception.  If I make a StupidMorph
which defines "model self shouldNotImplement", create a StupidMorph,
put some kind of text morph inside it, install a URL therein, and click
on the URL, this code will blow up.

In short, #respondsTo: is not really suitable for everyday use,
and its use here is wrong (defined as "apparently innocent changes
elsewhere may break it").

	  "if it's a morph, see if it is contained in a web browser"
	** The #isKindOf idiom **
	  (anObject isKindOf: Morph) ifTrue: [
	   m _ anObject.
	   [ m ~= nil ] whileTrue: [
	    (m isWebBrowser) ifTrue: [
	     m  jumpToUrl: url.
	     ^true ].

isKindOf: also suffers from correctly answering the wrong question.
For example, suppose I am trying to clean up Morphic, and start by
making a PseudoMorph class and trying to use it as a Morph.  Whenever
it is sent a message it doesn't understand, it tries to copy the
implementation from Morph.  The idea is to build up a short list of
messages that we KNOW are used, and we know something about WHEN they
are used.  THIS ACTUALLY HAPPENED, although it wasn't me.
The problem is that

    PseudoMorph isKindOf: Morph

answers 'false', whereas the whole point of PseudoMorph (not its real
name) was to act just like a Morph.  What's wanted is (wait for it):

    #isMorph

because that answers the right question.  Oh yes, PseudoMorph is also
a good example of why #respondsTo: doesn't always work.  Suppose I want
to know whether it will be OK to send the #bounds message to a
PseudoMorph.  It may be that the #bounds implementation hasn't been
copied over yet, so aPseudoMorph respondsTo: #bounds will answer false.
But if I actually _try_ it, the method will in fact be copied and
correctly responded to.

So just in case some clown has used #isKindOf: or #respondsTo:, you
have to reimplement those messages:

    PseudoMorph>>respondsTo: aSymbol
        ^(Morph canUnderstand: aSymbol or: [
          super respondsTo: aSymbol]

    PseudoMorph>>isKindOf: aClass
        ^aClass == Morph or: [
         Morph inheritsFrom: aClass or: [
         super isKindOf: aClass]]

And then, when you have finished, you have to remember to remove these
methods again.  Feh!

	** The #hasProperty idiom **
	    (m hasProperty: #webBrowserView) ifTrue: [
	     m model jumpToUrl: url.
	     ^true ].
	    m _ m owner. ]
	  ].
	
That so-called idiom is appropriate precisely because the thing
in question is a Morph; only Morphs have such properties.

	So, we have #isFoo, #respondsTo, #isKindOf, and #hasProperty.
	Which one is the "correct" way of doing it?

It is almost always the case that #respondsTo: and #isKindOf:
(note the trailing colons) answer the wrong questions.
The majority of objects do not implement #hasProperty.

That leaves #isFoo as the only one which is generally applicable.

	The #hasProperty could be argued that it's a different thing (I
	don't know what it means at this point) so we can leave that off
	for now, 

Of course it can.  There are three unrelated versions of #hasProperty
in Squeak 3.5.  (This is yet another reason why #respondsTo: is not at
all trustworthy:  an object may indeed have an implementation for the
selector you are asking about, but it may not *mean* by that selector
what you expect it to mean.)

Phoneme>>hasProperty:
    A Phoneme may have an IdentityDictionary mapping properties
    (represented by symbols) to values.  The features include
    things like #continuant, #vowel, #front, #back, #mid,
    #diphthong, #consonant, #stop, #unvoiced, #voiced, #fricative,
    #whisper, #nasal, #semivowel, #liquid, #glide, #affricate,
    #silence.  They don't include #webBrowserView.
    These properties may be added by #addProperty:, a method NOT
    shared by Morphs or B3dEngines.

B3DEngine>>hasProperty:
    A B3DEngine may have an IdentityDictionary mapping properties
    (represented by symbols) to values.  I have no idea what
    properties it may have.

Morph>>hasProperty:
    A Moprh may have an IdentityDictionary mapping properties
    (represented by symbols) to values.  There are lots of these
    properties, and different Morphs may have different sets of
    allowable properties.  Some documentation about which Morphs
    make sense of which properties would be a Good Thing.  There
    are things like #size and #webBrowserView.

It is fair to say that "properties" is a Squeak design pattern
for an open-ended set of publicly accesible and publicly mutable
"instance variables".

There is a serious problem with using the "properties" pattern,
and that is that the IDE cannot tell you what properties are used
or where they are used; there isn't even any reminder in the class
comment template to list properties with their meanings.

In this case, oddly enough, I think that #hasProperty may be _exactly_
the right tool for the job, because the method in question should be
scanning an object and its owners, all known to be Morphs,

As far as I can see, the only place that the #webBrowserView is
ever _set_ is in Scamper>>morphicWindow, where we find that the
particular kind of Morph that has this property is a SystemWindow.

The obvious way to implement the method we are discussing is simply

    anObject withAllOwnersDo: [:morph |
        (morph hasProperty: #webBrowserView) ifTrue: [
            ^morph model]]

	but the other three seem to try to accomplish the same thing.

Ah, no.  That's the point, really.  They do DIFFERENT things.

    anObject isKindOf: aClass
	"True iff anObject's class is aClass or a descendant of aClass.
         anObject's class and aClass need have no protocols or
         behaviours in common."

    anObject respondsTo: aSelector
	"True iff anObject's class implements or inherits aSelector
	 right at this instant; if true, the implementation might
	 always raise an exception; if false, an implementation
	 might magically appear anyway if you send aSelector."

    anObject isFoo
	"Answer whatever the comment in #isFoo implementations say.
         Generally means that anObject implements a "foo" protocol
         or is in a "foo" state."

	What I'm saying is that that code smells to me.


Me too.
Why else would I have discussed how to make it smell less bad?

	> THAT'S the way to clean up #isFoo messages in Object.
	
	So, you're saying to move the #isFoo's off of Object and put
	them on appropriate classes in the inheritance hierarchy.
	That was my suggestion #2.
	
No.  What I was saying is that *IF* you are going to move #isFoo
messages out of Object, *THEN* that is the way to do it, not to
add seriously insane general mechanisms that will make it worse.

	> See, the thing is that if you add a general mechanism, what you're REALLY
	> doing is evading the hard work of examining the code looking for a chance
	> to clean up and *eliminate* the bad smells.
	
	It sounds like you're agreeing with me that #isFoo is smelly.


No, NO, and *NO*.

I was saying that *that* one is.

The whole point that I keep on trying to make, and some people don't
seem to get at all, although it isn't very complicated, is that
    #isFoo messages as such are fine;
    *any* messages in Object deserve to be looked at really really hard;
    *some* messages which look as though they don't belong in Object
        really don't, but the right way to get rid of them is not to
        put them back in disguise, but to consider each such message
        very carefully and refactor;
    *some* messages which look as though they don't belong in Object
        are actually quite harmless and would be hard to replace without
        making things worse.

	So... what I'm hearing from you is that you agree with my
	suggestion #2--that #isFoo's should go in an appropriate place
	in the hierarchy and not in Object.  Right?

*NO*.  What I'm saying is that for *SOME* #isFoo messages,
Object *IS* the right place, but for *SOME* it isn't.

Got that?



More information about the Squeak-dev mailing list