Skip to main content
Engineering LibreTexts

16.7: Intercepting Messages Not Understood

  • Page ID
    39673
    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    So far we have used Pharo’s reflective features mainly to query and explore objects, classes, methods and the run-time stack. Now we will look at how to use our knowledge of its system structure to intercept messages and modify behaviour at run time.

    When an object receives a message, it first looks in the method dictionary of its class for a corresponding method to respond to the message. If no such method exists, it will continue looking up the class hierarchy, until it reaches Object. If still no method is found for that message, the object will send itself the message doesNotUnderstand: with the message selector as its argument. The process then starts all over again, until Object>>doesNotUnderstand: is found, and the debugger is launched.

    But what if doesNotUnderstand: is overridden by one of the subclasses of Object in the lookup path? As it turns out, this is a convenient way of realizing certain kinds of very dynamic behaviour. An object that does not understand a message can, by overriding doesNotUnderstand:, fall back to an alternative strategy for responding to that message.

    Two very common applications of this technique are 1) to implement lightweight proxies for objects, and 2) to dynamically compile or load missing code.

    Lightweight proxies

    In the first case, we introduce a minimal object to act as a proxy for an existing object. Since the proxy will implement virtually no methods of its own, any message sent to it will be trapped by doesNotUnderstand:. By implementing this message, the proxy can then take special action before delegating the message to the real subject it is the proxy for.

    Let us have a look at how this may be implemented.

    We define a LoggingProxy as follows:

    ProtoObject subclass: #LoggingProxy
        instanceVariableNames: 'subject invocationCount'
        classVariableNames: ''
        package: 'PBE-Reflection'
    

    Note that we subclass ProtoObject rather than Object because we do not want our proxy to inherit around 400 methods (!) from Object.

    Object methodDict size
    >>> 397
    

    Our proxy has two instance variables: the subject it is a proxy for, and a count of the number of messages it has intercepted. We initialize the two instance variables and we provide an accessor for the message count. Initially the subject variable points to the proxy object itself.

    LoggingProxy >> initialize
        invocationCount := 0.
        subject := self.
    
    LoggingProxy >> invocationCount
        ^ invocationCount
    

    We simply intercept all messages not understood, print them to the Transcript, update the message count, and forward the message to the real subject.

    LoggingProxy >> doesNotUnderstand: aMessage
        Transcript show: 'performing ', aMessage printString; cr.
        invocationCount := invocationCount + 1.
        ^ aMessage sendTo: subject
    

    Here comes a bit of magic. We create a new Point object and a new LoggingProxy object, and then we tell the proxy to become: the point object:

    point := 1@2.
    LoggingProxy new become: point.
    

    This has the effect of swapping all references in the image to the point to now refer to the proxy, and vice versa. Most importantly, the proxy’s subject instance variable will now refer to the point!

    point invocationCount
    >>> 0
    point + (3@4)
    >>> 4@6
    point invocationCount
    >>> 1
    

    This works nicely in most cases, but there are some shortcomings:

    point class
    >>> LoggingProxy
    

    Actually the method class is implemented in ProtoObject, but even if it were implemented in Object, which LoggingProxy does not inherit from, it isn’t actually send to the LoggingProxy or its subject. The message is directly answered by the virtual machine. yourself is also never truly sent.

    Other messages that may be directly interpreted by the VM, depending on the receiver, include:

    +- < > <= >= = ~= * / \ ==@ bitShift: // bitAnd: bitOr:at: at:put:
    size next nextPut: atEnd blockCopy: value value: do: new new: x y.
    

    Selectors that are never sent, because they are inlined by the compiler and transformed to comparison and jump bytecodes:

    ifTrue: ifFalse: ifTrue:ifFalse: ifFalse:ifTrue: and: or: whileFalse:
    whileTrue: whileFalse whileTrue to:do: to:by:do: caseOf:
    caseOf:otherwise: ifNil: ifNotNil: ifNil:ifNotNil: ifNotNil:ifNil:
    

    Attempts to send these messages to non-boolean normally results in an exception from the VM as it can not use the inlined dispatching for non-boolean receivers. You can intercept this and define the proper behavior by overriding mustBeBoolean in the receiver or by catching the NonBooleanReceiver exception.

    Even if we can ignore such special message sends, there is another fundamental problem which cannot be overcome by this approach: self-sends cannot be intercepted:

    point := 1@2.
    LoggingProxy new become: point.
    point invocationCount
    >>> 0
    point rectangle: (3@4)
    >>> 1@2 corner: 3@4
    point invocationCount
    >>> 1
    

    Our proxy has been cheated out of two self-sends in the rect: method:

    Point >> rect: aPoint
        ^ Rectangle origin: (self min: aPoint) corner: (self max: aPoint)
    

    Although messages can be intercepted by proxies using this technique, one should be aware of the inherent limitations of using a proxy. In Section 16.7 we will see another, more general approach for intercepting messages.

    Generating missing methods

    The other most common application of intercepting not understood messages is to dynamically load or generate the missing methods. Consider a very large library of classes with many methods. Instead of loading the entire library, we could load a stub for each class in the library. The stubs know where to find the source code of all their methods. The stubs simply trap all messages not understood, and dynamically load the missing methods on demand. At some point, this behaviour can be deactivated, and the loaded code can be saved as the minimal necessary subset for the client application.

    Let us look at a simple variant of this technique where we have a class that automatically adds accessors for its instance variables on demand:

    DynamicAcccessors >> doesNotUnderstand: aMessage
        | messageName |
        messageName := aMessage selector asString.
        (self class instVarNames includes: messageName)
            ifTrue: [
                self class compile: messageName, String cr, ' ^ ', messageName.
                ^ aMessage sendTo: self ].
        ^ super doesNotUnderstand: aMessage
    

    Any message not understood is trapped here. If an instance variable with the same name as the message sent exists, then we ask our class to compile an accessor for that instance variables and we re-send the message.

    Suppose the class DynamicAccessors has an (uninitialized) instance variable x but no pre-defined accessor. Then the following will generate the accessor dynamically and retrieve the value:

    myDA := DynamicAccessors new.
    myDA x
    >>> nil
    

    Let us step through what happens the first time the message x is sent to our object (see Figure \(\PageIndex{1}\)).

    Dynamically creating accessors.
    Figure \(\PageIndex{1}\): Dynamically creating accessors.

    (1) We send x to myDA, (2) the message is looked up in the class, and (3) not found in the class hierarchy. (4) This causes self doesNotUnderstand: #x to be sent back to the object, (5) triggering a new lookup. This time doesNotUnderstand: is found immediately in DynamicAccessors, (6) which asks its class to compile the string 'x ^ x'. The compile method is looked up (7), and (8) finally found in Behavior, which (9-10) adds the new compiled method to the method dictionary of DynamicAccessors. Finally, (11-13) the message is resent, and this time it is found.

    The same technique can be used to generate setters for instance variables, or other kinds of boilerplate code, such as visiting methods for a Visitor.

    Note the use of Object>>perform: in step (12) which can be used to send messages that are composed at run-time:

    5 perform: #factorial
    >>> 120
    
    6 perform: ('fac', 'torial') asSymbol
    >>> 720
    
    4 perform: #max: withArguments: (Array with: 6)
    >>> 6
    

    This page titled 16.7: Intercepting Messages Not Understood is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.