An override of #doesNotUnderstand: *is* (an instance of) the problem.
What part of "you may think you know what to forward now, but just
wait a couple of months or install an additional package and there
will be *more* selectors you needed to think about but didn't" is
hard to understand?

Proxies done right are proxies *relative to a specified protocol*.

Let me give you a personal example.
I wanted something that was like an OrderedCollection but
could only hold elements of a specific class.
No worries, just make a proxy.  Make a bare-bones object,
define all the #add... methods to check their argument,
and use #doesNotUnderstand to forward everything else to
the underlying OrderedCollection.

The first problem is the selectors that the bare-bones
object *does* understand by virtue of being an object.
(Remember there are *hundreds* of these selectors in
Squeak, Pharo, VisualWorks, even in gst there are more
than 120).  You must, for example, ensure that #instVarAt:
is forwarded, not handled locally, BUT you must also
ensure that #instVarAt:put: is handled locally, not forwarded.
And then one day you find that your program has broken,
because mutable collections now have
  aMutableCollection inPlaceCollect: collectBlock
and that can put unacceptable results in the collection
*without* going through any #add... method.

Eventually you realise "handing out a wrapper for this
collection constrained to only allow adding certain things"
is the wrong way to do it, and you do something like

    constrainedAdder: constraint
      ^[:x | (constraint value: x)
               ifTrue:  [self add: x]
               ifFalse: [DomainError receiver: self selector: #add:]]

or you do something else entirely, like handing out a wrapper with
a *narrow* interface that doesn't make the slightest pretence of
*being* the other object.

I *could* make a batch of ricin and store it in my
granddaughter's bedroom, but I'm not going to.
I *could* make a proxy by going through #doesNotUnderstand:
but I am not going to.  That would be a textbook example of
maxim 38: "Just because it's easy for you doesn't mean it
can't be hard on your clients."






On Sun, 20 Mar 2022 at 15:18, James Foster <smallt...@jgfoster.net> wrote:

> I don’t understand. Wouldn’t an override of #'doesNotUnderstand:’ solve
> this problem? The proxies I’ve seen subclass from nil or ProtoObject and
> forward almost everything to the target. It’s really very easy.
>
> On Mar 19, 2022, at 3:14 AM, Richard O'Keefe <rao...@gmail.com> wrote:
>
> An object should be a Proxy or Stub only with reference to a specific
> protocol, which should be kept narrow.
>
> Being a Proxy is a form of coupling.  Proxying a wide
> interface creates maintenance problems:
> Squeak 5.3 : Object selectors size => 485
> Pharo 9.0  : Object selectors size => 435
> astc       : Object selectors size => 325
> VW 8.3PUL  : Object selectors size => 304
>
> The interface of Object is HUGE.  You want to bet that
> your Proxy got *all* of the methods right?  This
> interface didn't get that way all at once; it grew.
> The number was 78 in Smalltalk-80.  At a minimum, then,
> Smalltalk systems have accreted one extra Object method
> every two months.
>
> So you set up your proxy to *flawlesly* mirror Object,
> and then, WHOOPS, upgrade Smalltalk and now there is a
> method that Object has and your Proxy either lacks (if
> it descends from ProtoObject but not Object) or inherits
> an inappropriate version of (if it descends from Object).
>
> What this means is that nobody ever *does* flawlessly
> mock everything in the public interface of an object
> they are Proxying.  They proxy a *limited* protocol.
> Because that is all they *can* do.
>
> Look, I know that people who have been trained to work
> with the stuff can use C4 as cooking fuel.  But I haven't
> had that training, so I won't touch the stuff.  In the
> same way, I dare say there are things *you* can safely
> do in Smalltalk that fumblefingers here would be burnt
> badly by.  There are many things that *can* be done that
> I *won't* do.  In a chemistry lab, I would not work with
> ClF3 let alone O2F2.  In Smalltalk, I don't monkey with
> #isNil.
>
> On Fri, 18 Mar 2022 at 03:52, James Foster <smallt...@jgfoster.net> wrote:
>
>> Richard,
>>
>> I very much admire Dijkstra’s admonition regarding “The Humble
>> Programmer” and was pointing a student to that article just this week.
>>
>> In any case, I think you’ve demonstrated that you now comprehend the
>> argument against inlining—you just don’t agree. That’s fair and I think the
>> discussion has been clarified. Would it be fair to say that you have an
>> “ideological objection” to allowing a Proxy or Stub to transparently stand
>> in for another object (say, in a two-object-space environment such as Pharo
>> and GemStone)? That is, a domain object can’t be replaced by a Proxy or
>> Stub without a wholesale rewrite of the rest of the application? I respect
>> that as a reasonable position (demanding perfect clarity), but I see a cost
>> to that position as well.
>>
>> Of course, if you really want to avoid allowing the receiver to chose its
>> response to a message, you can use other messages. So if you want to find
>> out if an object is identical to nil you should use `nil == myObject` to
>> ensure that there was not an override of #’isNil’ or #’==‘ by the object’s
>> class.
>>
>> James
>>
>> On Mar 17, 2022, at 2:27 AM, Richard O'Keefe <rao...@gmail.com> wrote:
>>
>> My chief concern is that I am a bear of very little brain,
>> and if you change the meaning of #isNil to anything at all
>> other than "is the receiver identical to nil" you *WILL*
>> (not may) confuse me.  This extends to things that happen
>> not to be inlined: if even a god-like Smalltalker like
>> Andres Valloud overloads #, to something other than "combine
>> the collection that is the receiver with the collection that
>> is the argument to yield a new collection" than I *WILL*
>> most certainly be confused and find the code unmaintainable.
>> Smalltalk being Smalltalk, if you admit an inconsistent
>> overload anywhere, I can no longer understand sends of that
>> selector anywhere.  One of the things to like about Traits
>> is that you can say "this class doesn't just *happen* to
>> have selectors x and y, it has them *because* it has this
>> whole consistent bundle of selectors."
>>
>> There are more annotations documented for my Smalltalk
>> compiler than are actually implemented.  One that *is*
>> implemented is <doNotOverride>, and it has caught more
>> mistakes than I care to admit to.  It's particularly
>> important for a bundle of methods with varying arguments
>> that are meant to be routed through a single method,
>> which *is* meant to be overridden.  It makes sure that
>> I override the *right* method.  (Take #= and #~= as an
>> obvious example.)
>>
>> Once you start down the path of lying about things like #isNil
>> you find that EITHER you have to go very far down that path
>> and override #== and #instVarAt: and a whole lot of other
>> things OR you are working with a semantically incoherent system.
>>
>> "How should a proxy (https://en.wikipedia.org/wiki/Proxy_pattern) to nil
>> respond to the #’isNil’ message?"
>>
>> It SHOULD NOT LIE.  A proxy *isn't* nil, and it doesn't *behave* like nil,
>> even if it is a proxy for nil.  A proxy, qua proxy, can do things that nil
>> cannot.  Use another selector, #isEffectivelyNil, or whatever reveals your
>> intentions, and give it what semantics you find useful.
>>
>> "How should the Null Object Pattern (
>> https://en.wikipedia.org/wiki/Null_object_pattern) respond to #’isNil’?"
>>
>> It should answer false.  Period.  No ifs, buts, quibbles, or maybes.
>> The whole *point* of the Null Object Pattern is to return something
>> that *isn't* nil, that has quite a different protocol.  If you call
>> something that is supposed to return either a Foo or a NullFoo, and
>> it gives you nil instead, there is a BUG in what you just called so
>> the sooner you find out the better.  What you want is
>>
>>    Foo >> isNullFoo ^false
>>    NullFoo >> isNullFoo ^true
>> and no other class defines #isNullFoo.
>>
>> That way, when you ask "tripeWorks grobblingMill lastPallet isNullPallet"
>> (a) you make it clear to someone reading your code what you are expecting
>> (b) if you DON'T get what you are expecting, Smalltalk will tell you.
>>
>> I must admit that on the few occasions when I've used Null Object
>> I've used an all-purpose #isMissing instead of a task-appropriate
>> #isNullFoo, but then I figured out what I was doing wrong.  You
>> look at the code, you say "there's *something* off here, but I don't
>> know what."  But when you ask "what, exactly, does this method MEAN?"
>> you realise "oh, THAT'S what I was doing wrong."  #isMissing told me
>> it was a NullPerson or a NullAddress or a NullPartsList or ... but
>> in this case I needed to know whether it was a NullSummary.
>>
>> And of course you run into E.F.Codd's lesson: "one NULL is never
>> enough, information can be missing for more than one reason".
>> Take the familiar example of a phone number:
>>  - I know that Fred's phone number is X
>>  - I know that Fred has a phone but I don't know what the number is
>>  - I don't know whether Fred has a phone or not
>>  - I know that Fred has no phone
>> There's room for three *different* null objects there.
>> Should we have UnknownNumberOfActualPhone to answer false to #isnil
>> and NonNumberOfNonexistentPhone to answer true?  Or what?
>>
>> By the way, you may have misunderstood my benchmark.
>> It wasn't that #isNil or even _ ifNotNil: speeded up by
>> 10%, it was the *whole* matrix-munching benchmark that
>> speeded up.  Certainly not a big deal, but it's not
>> something I'd be happy to give up in order to get less
>> maintainable code.
>>
>>
>>
>>
>>
>>
>>
>>
>> On Thu, 17 Mar 2022 at 18:21, James Foster <smallt...@jgfoster.net>
>> wrote:
>>
>>> Richard,
>>>
>>> My _concern_ with inlining is that since it is designed to short-circuit
>>> dynamic method lookup, it is impossible to call a _different_
>>> implementation. That is, you lose the opportunity to have the _receiver_
>>> decide how to respond to the message. You may think of it as a message, but
>>> the caller is deciding how the receiver will respond—which largely defeats
>>> the purpose and role of it being a message. Yes, at the machine code level
>>> you are performing a branch instruction, but when comparing OOP to
>>> Procedural Programming we typically make a distinction between “messages”
>>> and "procedure calls." The distinction is that the receiver gets to decide
>>> how to respond to a message. In C++ this is the distinction between a
>>> “virtual" and "non-virtual" function. By inlining, you are converting the
>>> function from a virtual function to a non-virtual function, and this can
>>> make a difference (which is why virtual functions exist).
>>>
>>> How should a proxy (https://en.wikipedia.org/wiki/Proxy_pattern) to nil
>>> respond to the #’isNil’ message? How should the Null Object Pattern (
>>> https://en.wikipedia.org/wiki/Null_object_pattern) respond to #’isNil’?
>>>
>>> And, yes, I’m sure you can come up with benchmarks that show a
>>> measurable difference, but what is the impact in realistic code? When
>>> someone asked about inlining #’yourself’ in GemStone I believe I measured
>>> the performance as taking 2 nanoseconds per call (on a 2012 machine). A 10%
>>> speedup would make it 1.8 nanoseconds. Is that worth it? Maybe, maybe not.
>>>
>>> Note that I described my position as a “concern,” not an ideological
>>> objection. Mostly I’m giving a rationale for something that doesn’t seem to
>>> be explained very well for you. I accept that there may be a time for
>>> inlining, but I can “comprehend" another side to the issue.
>>>
>>> James
>>>
>>> On Mar 16, 2022, at 9:42 PM, Richard O'Keefe <rao...@gmail.com> wrote:
>>>
>>> We're still not on the same page.
>>> You seem to have some ideological objection to inlining that
>>> I am completely failing to comprehend.
>>> Just because a procedure call (message send) is inlined doesn't
>>> in the least mean it *isn't* a procedure call (message send),
>>> just as compiling a procedure call (message send) as a jump
>>> (last-call optimisation) doesn't mean it *isn't* a procedure
>>> call (message send).
>>> By the way, forget about "40 years ago".
>>> I just did an experiment in Pharo 9, and found that
>>> using "_ ifNotNil: " instead of "_ izNil ifFalse: "
>>> -- where izNil is a non-inlined self == nil --
>>> gave a 10% speedup, in a test code where real work was going
>>> on as well.
>>> As for turning off all inlining, what do you think that would
>>> do to #ifFalse:ifTrue: and its relatives?
>>>
>>>
>>> On Thu, 17 Mar 2022 at 08:34, <s...@clipperadams.com> wrote:
>>>
>>>>
>>>> To start with, why do you CARE whether a particular method is inlined
>>>> or not?
>>>>
>>>> I care because it makes “everything is a message” a lie! And I suspect
>>>> (no proof and could be wrong) it’s an optimization that only made sense
>>>> with the hardware constraints of 40+ years ago. Arguing against premature
>>>> optimization is hardly something I just made up ;-)
>>>>
>>>> This makes absolutely no sense to me. What makes you think that the
>>>> combination "_ isNil ifFalse: [_]" will NOT be inlined?
>>>>
>>>> I may have been unclear. My intent was to communicate: “I’d like to
>>>> stop ALL* inlining of messages by default if possible”
>>>>
>>>> *or as many as practical
>>>>
>>>> The thing that rings loud alarm bells for me is there being "long
>>>> chains" in the first place.
>>>>
>>>> I agree that it is in general a smell, but long chains was tangential
>>>> to the intention above
>>>>
>>>> Can you give an example?
>>>>
>>>> I don’t know if I can think of one that’s not contrived… Wrapping
>>>> something external? Squeak’s AppleScript support used to mirror the
>>>> underlying AS, which is pretty much exactly that.
>>>>
>>>> In my own programming, I've generally found that nils turning up in the
>>>> middle of a chain indicates a serious design error somewhere.
>>>>
>>>> Agreed. See smell comment above.
>>>>
>>>
>>>
>>
>

Reply via email to