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. >>>> >>> >>> >> >