[squeak-dev] what is a transparent proxy?

Eliot Miranda eliot.miranda at gmail.com
Tue Jan 18 02:02:50 UTC 2022

Hi Christoph, Hi All,

On Sun, Jan 16, 2022 at 9:21 AM <christoph.thiede at student.hpi.uni-potsdam.de>

> Hi Chris,
> my understanding of a "transparent proxy" is that it automatically
> forwards all requests, thus requiring special metaprogramming logic to
> reveal an object as a proxy. On the other hand, "intransparent proxies"
> would define an explicit list of requests to forward. Not sure however
> whether this is a useful classification ...
> > > - ProtoObject is still not yet an empty class. For instance, stuff
> such as #doOnlyOnce: or #scaledIdentityHash should be implemented on Object
> only. We should continue work on this in the Trunk. You can mitigate this
> by redefining these messages in your proxy and forwarding them yourself.
> >
> > It would arguably be a more elegant way of organizing code, but not much
> else.
> One central difference it that only an empty ProtoObject allows you for
> defining a truly transparent proxy. Otherwise, you are required to override
> a larger number of messages and forward each of them yourself.

+1.  Squeak's ProtoObject has far too many methods.  It should have
doesNotUnderstand: and nothing else except selectors that are understood to
be proxy-specific and not for use in "normal" code.

> > > - Primitive methods do not resolve proxies automatically, i.e., #(1 2
> 3) at: (MyProxy1 for: 2) would have the primitive failed. My mitigation for
> this problem was to define an exclusion list for certain classes that
> should never be wrapped into a proxy, but this is not an ideal solution,
> >
> > My mitigation strategy was a simple "yourself" on the end. It instantly
> gives away to future developers familiar with writing proxy-aware code. It
> seems like you're willing to go through to avoid that.
> Well, this might depend on the use case of your proxies. I have a proxy
> class that automatically wraps every answer into another proxy to decorate
> an entire object graph, so an exclusion list for certain types felt like
> the right approach to me. Building a framework, I have also no control
> about to which primitives the client sends these answered instances.
> > Now you're talkin'! :-) Proxy-aware code!!
> I really think this would a small pattern which should be no huge effort
> to apply to all <primitive:> sends in the Trunk. :-) How could we best
> design such a pattern? For instance, looking at Object >> #at:
>     *at:* index
>         <*primitive:* 60>
>         index isInteger ifTrue:
>             [self class isVariable
>                 ifTrue: [self errorSubscriptBounds: index]
>                 ifFalse: [self errorNotIndexable]].
>         index isNumber
>             ifTrue: [^self at: index asInteger]
>             ifFalse: [self errorNonIntegerIndex]
> We could insert this statement at the beginning of the method:
>     (index isInteger and: [((thisContext objectClass: index)
> includesBehavior: Integer) not]) ifTrue:
>         [^ self at: index yourself].

Yes, ok.   But better would be to introduce a set of selectors understood
by ProtoObject and the relevant classes for checking arguments to
primitives etc, i.e. for checking concrete types.  So ProtoObject and
SmallInteger could implement isBasicSmallInteger and the code would read

    *at:* index
        <*primitive:* 60>
        index isBasicSmallInteger ifTrue:
            [self basicClass isVariable
                ifTrue: [self errorSubscriptBounds: index]
                ifFalse: [self errorNotIndexable]].
        index isNumber
            ifTrue: [^self at: index asInteger]
            ifFalse: [self errorNonIntegerIndex]

Object>>#perform:withArguments:inSuperclass: could be rewritten like this:
>     *perform:* selector *withArguments:* argArray *inSuperclass:*
> lookupClass
>         <*primitive:* 100>
>     +    (selector isSymbol and: [((thisContext objectClass: selector)
> includesBehavior: Symbol) not])
>             ifTrue: [^ self perform: selector yourself withArguments:
> argArray inSuperclass: lookupClass].
>         (selector isSymbol)
>             ifFalse: [^ self error: 'selector argument must be a Symbol'].
>         (selector numArgs = argArray size)
>             ifFalse: [^ self error: 'incorrect number of arguments'].
>         (self class == lookupClass or: [self class inheritsFrom:
> lookupClass])
>             ifFalse: [^ self error: 'lookupClass is not in my inheritance
> chain'].
>         self primitiveFailed
> And so on for almost all the other primitive users as well.
> For #==, it would be harder because our current semantics of identity say
> that a proxy has its own identity, and it would probably be a bad decision
> to change this semantics. Maybe we would need a differentiation between
> "proxy-aware identity" and "effective identity", and #= (like in Symbol >>
> #=) would be one of many cases where the latter concept would be required.
> But complexity is increasing right now ...
> See also the discussion about the right place for the mirror protocol. [1]
> Any other ideas how we could minify/beautify the above pattern? :-)
> Best,
> Christoph
> [1]
> http://lists.squeakfoundation.org/pipermail/squeak-dev/2022-January/218221.html
> ---
> *Sent from **Squeak Inbox Talk
> <https://github.com/hpi-swa-lab/squeak-inbox-talk>*
> On 2022-01-15T18:51:00-06:00, asqueaker at gmail.com wrote:
> > Hi Craig,
> >
> > > Indeed, none of our code should be proxy-aware.
> >
> > I certainly would agree with the statement, "it would be nice if none
> > of our code needed to be proxy-aware," but I don't yet understand how
> > it's possible, regardless where the implementation details are handled
> > (VM or image). As a VM expert, I can appreciate you preferring to use
> > VM-based proxies. The only downside is that those who only understand
> > Smalltalk and not the secret innards of the VM cannot realistically
> > participate in Squeak anymore if they wish or need to use the classic
> > Proxy pattern. We broke the code and also legacy systems for that.
> >
> > Which is why I keep bringing up just this one example on Symbol
> > and hoping someone can explain it to help me get unstuck. Would you
> > follow its code with me, line by line, below? Let's pretend the
> > receiver is the symbol #size, and the argument, aSymbol, comes in as
> > an instance of MyProxy, which refers to an Integer oid that ultimately
> > will reify to the receiver object (#size)).
> >
> > It's easy to see why the code no longer works for the traditional Proxy
> pattern:
> >
> > = aSymbol "<------ aSymbol comes in as a MyProxy instance"
> > self == aSymbol ifTrue: [^ true]. "<---- false, continue"
> > aSymbol isSymbol ifTrue: [^ false]. "<----- true, return false. BUG!"
> > "Use String comparison otherwise"
> > ^ super = aSymbol
> >
> > So what about when aVMProxy instance is passed in? I'm so used to
> > normal Smalltalk-think, I'm not even able to think about WHEN or HOW
> > the VM would perform it's magic handling of the incoming VMProxy
> > instance to make this work. I guess this is a trade-off -- we don't
> > have to think about Proxy's, but we have to know about and think about
> > VM magic. I'm willing to try, but I don't even know where to begin.
> > Would you help?
> >
> > Regards,
> > Chris

best, Eliot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220117/dbce5a52/attachment-0001.html>

More information about the Squeak-dev mailing list