Object: Identity vs. Environment

Richard A. O'Keefe ok at cs.otago.ac.nz
Mon Jun 9 05:11:44 UTC 2003


Joel Shellman <joel at ikestrel.com> continues the #isFoo debate.
	> in Color, one would have to write
	>
	>     fulfills: aSymbol
	>         ^aSymbol == #Colorness or: [super fulfills: aSymbol].
	>
	> That is, every method which asserts that instances of a class have
	> some property would be MORE complicated than the corresponding
	> #isFoo method would have been.
	
	No, you wouldn't do it that way--as you say, that would indeed
	be bad.  The entire implementation could be in Object.  Object
	would create a dictionaryOfFulfilling and then:

	Object>>fulfills: aSymbol
	    ^dictionaryOfFulfilling containsKey: aSymbol

I am having some trouble believing that someone would go to the
extreme of adding an extra instance variable to EVERY OBJECT just so
that a bunch of #isFoo methods can be replaced by a vastly less
efficient approach which is based on coupling that would make the
most hardened software engineer weep.

What have is #isColour and #isCollection to do with each other
beyond starting with "is"?  Practically nothing.  Stuffing them
into one method, so that you have to ask
    thingy fulfills: #Collection
instead of
    thingy isCollection
makes code HARDER to read, HARDER to write, SLOWER to run, LESS
maintainable, et cetera ad nauseam.  To be asked to pay the heavy
price of an extra instance variable per object in order to suffer
with such a nauseatingly bad design is too much.

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.
In particular, Terry Pratchett fans will understand why

    angua isBeautifulWoman
    angua isWolf

have time-dependent behaviour, and the time dependency is not necessarily
the same as the time dependency for other other object of the same class.
(Angua is a werewolf working for the Ankh-Morpork City Watch.)

And indeed, we find that the popular #isEmpty method *does* have a time
varying result which *is* different for different objects in the same
class.

Now, imagine a kind of object with the following life-cycle:
    create; populate; freeze; use as stream;
    (thaw; repopulate; freeze; use as stream)*;
    destroy
At some times, it is willing to act as a stream (after a freeze and before
the next thaw or destroy); at others it is not.  So

    isStream
        ^frozen

would be the right time-dependent behaviour for it.  I think this is a
perfectly reasonable thing to do.

	(I guess this would be a class method or something, but I'm
	still new with smalltalk.)
	
Well, it *can't* be a class method, because it is *invoked* on objects.
You don't ask
         vvvvv
    arg3 class isColor ifFalse: [...]
         ^^^^^
but just plain arg3 isColor ifFalse: [...].

So I guess you are saying that one should ask

    (arg3 fulfills: #Color) ifFalse: [...]

and that Object should contain

    fulfills: aProperty
        ^self class propertySet includes: aProperty

and that each class would have a class instance variable
(let's call it propertySet) and that we'd have

    Color class>>
    propertySet
        propertySet ifNil: [
	    propertySet := super copy add: #Color; yourself].
	^propertySet

Feh!  Why should we encumber ourselves with all this appalling code
just to eliminate isBehavior, isCollection, isColor, isFloat, isForm,
isFraction, isInteger, isMessageSend, isMorph, isMorphicEvent, isNumber,
isPoint, isPseudoContext, isString, isText, isTransparent,
isVariableBinding, and isWebBrowser from Object?

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!"  But leaving the ability to
say "yes I am/am not a Variable Binding" as part of the behaviour of
every object while only making it harder to invoke and implement that
behaviour is just silliness.  We have better things to do.

Come to think of it, with the new compiler I've heard of, maybe
isVariableBinding has already gone?

Let's take #isWebBrowser.  Where is that mentioned?

    FlashSpriteMorph getWebBrowser "private"
	self withAllOwnersDo: [:morph |
	    morph isWebBrowser ifTrue: [^morph].
	    (morph hasProperty: #webBrowserView) ifTrue: [^morph model]].
	^nil

Interestingly enough, this already uses a semi-general "property" hack.
As far as I can see, there are only two implementations of #isWebBrowser;
the one in Object which answers false and the one in Scamper which answers
true.  But an instance of Scamper is a Model, not a Morph.  I do not see
how the query "morph isWebBrowser" could ever come out true.  It may be
that this method could be simplified to

    FlashSpriteMorph getWebBrowser "private"
	"An instance of Scamper may be the model of one of my owners."
        self withAllOwnersDo: [:morph |
            (morph hasProperty: #webBrowserView) ifTrue: [^more model]].
	^nil

The second mention of #isWebBrowser is MethodFinder>>initialize,
and I think #isWebBrowser should be removed from that table.

The third and last mention of #isWebBrowser is

    TextURL>>
    actOnClickFor: anObject
        |response m|
        
        (url beginsWith: 'sqPr://') ifTrue: [
	    ProjectLoading thumbnailFromUrl: (url copyFrom: 8 to: url size).
	    ^self].

	"if it's a web browser, tell it to jump"
==>	anObject isWebBrowser
	    ifTrue: [anObject jumpToUrl: url. ^true].
==>	((anObject respondsTo: #model) and: [anObject model isWebBrowser])
	    ifTrue: [anObject model jumpToUrl: url. ^true].

	"if it's a morph, see if it is contained in a web browser"
	(anObject isKindOf: Morph) ifTrue: [
	    m := anObject.
	    [m ~~ nil] whileTrue: [
		"See comment about FlashSpriteMorph"
==>	        m isWebBrowser
	            ifTrue: [m jumpToUrl: url. ^true].
	        (h hasProperty: #webBrowserView)
	            ifTrue: [m model jumpToUrl: url. ^true].
		m := m owner].
	
	"more stuff not mentioning isWebBrowser".

Once again, we have something probing a Morph's owner chain looking for
a web browser.  Nothing both answers true to #isWebBrowser and is a Morph,
it looks as though "m isWebBrowser" must always answer false.

Looking at the calls of actOnClickFor:, it looks as though the argument
is always a Model or a Morph of some kind.

Perhaps
    #getWebBrowser could move into Morph
    #isWebBrowser could move into Model
and with the obvious simplifications here and there, there would then
be no need for #isWebBrowser in Object.

THAT'S the way to clean up #isFoo messages in Object.

	And then in any object/class, whenever it became such that it
	would fulfill for a given aSymbol (likely during initialization,
	but if during runtime it could morph into it, then runtime could
	add or remove it at will) it would add that symbol to it's
	dictionaryOfFulfilling.

Now he's saying that each object _does_ need its own new instance variable
for #fulfills:.  I quite agree, but I don't like the idea of having to add
a new instance variable to every object.

	That's one way, there are others, but this is simple enough.
	It's a simplistic dynamic way of declaring arbitrary marker
	interfaces.

Well, its rather too complicated to be called simplistic.
Complicated, inefficient, badly coupled, space hungry,
there are lots of words for it, but not simplistic.

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.
	
Did I mention that at least with #isFoo you get a chance to use
Cmd-m and Cmd-n to hunt down where the thing is defined and used, so that
you can figure out what's going on, whereas #fulfills: would hide
extremely important information from the IDE?  Just another reason to
detest #fulfills:, as if we hadn't enough already.
	



More information about the Squeak-dev mailing list