Skip to main content
Engineering LibreTexts

16.2: Introspection

  • Page ID
    39668
    \( \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}}\)

    Using the inspector, you can look at an object, change the values of its instance variables, and even send messages to it.

    Evaluate the following code in a playground:

    w := GTPlayground openLabel: 'My Playground'.
    w inspect
    

    This will open a second playground and an inspector. The inspector shows the internal state of this new playground, listing its instance variables on the left (borderColor, borderWidth, bounds...) and the value of the selected instance variable on the right. The bounds instance variable represents the precise area occupied by the playground.

    Now choose the inspector and click the playground area of the inspector which has a comment on top and type self bounds: (Rectangle origin: 10@10 corner: 300@300 ) in it of select as shown in Figure \(\PageIndex{1}\) and then Do It like you do with a code of a Playground.

    Inspecting a Workspace.
    Figure \(\PageIndex{1}\): Inspecting a Workspace.

    Immediately you will see the Playground we created change and resize itself.

    Accessing instance variables

    How does the inspector work? In Pharo, all instance variables are protected. In theory, it is impossible to access them from another object if the class doesn’t define any accessor. In practice, the inspector can access instance variables without needing accessors, because it uses the reflective abilities of Pharo. Classes define instance variables either by name or by numeric indices. The inspector uses methods defined by the Object class to access them: instVarAt: index and instVarNamed: aString can be used to get the value of the instance variable at position index or identified by aString, respectively. Similarly, to assign new values to these instance variables, it uses instVarAt:put: and instVarNamed:put:.

    For instance, you can change the value of the w binding of the first workspace by evaluating:

    w instVarNamed:'bounds' put: (Rectangle origin: 10@10 corner: 500@500).
    

    Important

    Caveat: Although these methods are useful for building development tools, using them to develop conventional applications is a bad idea: these reflective methods break the encapsulation boundary of your objects and can therefore make your code much harder to understand and maintain.

    Both instVarAt: and instVarAt:put: are primitive methods, meaning that they are implemented as primitive operations of the Pharo virtual machine. If you consult the code of these methods, you will see the special pragma syntax <primitive: N> where N is an integer.

    Object >> instVarAt: index
        "Primitive. Answer a fixed variable in an object. ..."
        
        <primitive: 173 error: ec>
        self primitiveFailed
    

    Any Pharo code after the primitive declaration is executed only if the primitive fails. This also allows the debugger to be started on primitive methods. In this specific case, there is no way to implement this method, so the whole method just fails.

    Other methods are implemented on the VM for faster execution. For example some arithmetic operations on SmallInteger:

    * aNumber
        "Primitive. Multiply the receiver by the argument and answer with the
        result if it is a SmallInteger. Fail if the argument or the result
            is not a
        SmallInteger. Essential. No Lookup. See Object documentation
            whatIsAPrimitive."
        
        <primitive: 9>
        ^ super * aNumber
    

    If this primitive fails, for example if the VM does not handle the type of the argument, the Pharo code is executed. Although it is possible to modify the code of primitive methods, beware that this can be risky business for the stability of your Pharo system.

    Instance variables of a GTPlayground.
    Figure \(\PageIndex{2}\): Displaying all instance variables of a GTPlayground.

    Figure \(\PageIndex{2}\) shows how to display the values of the instance variables of an arbitrary instance (w) of class GTPlayground. The method allInstVarNames returns all the names of the instance variables of a given class.

    GTPlayground allInstVarNames
    >>>
    #(#registry #suspendAll #suspendedAnnouncemets #logger #pane #title
        #titleIcon #transformation #actions #condition #implicitNotNil
        #dynamicActionsBlock #color #customValidation #shouldValidate
        #acceptsSelection #parentPrototype #registeredAnnouncers
        #updateActions #selectionActions #selectionDynamicActionsBlock
        #implicitAllNil #rawSelectionTransmissions #statusPane #sourceLink
        #initializationBlock #cachedDisplayedValue #labelActionBlock
        #portChangeActions #wantsSteps #stepTime #stepCondition
        #presentations #arrangement)
    
    w := GTPlayground someInstance.
    w class allInstVarNames collect: [:each | each -> (w instVarNamed:
        each)] 
    

    In the same spirit, it is possible to gather instances that have specific properties iterating over instances of a class using an iterator such as select:. For instance, to get all objects who are directly included in the world morph (the main root of the graphical displayed elements), try this expression:

    Morph allSubInstances
        select: [ :each |
                | own |
                own := (each instVarNamed: 'owner').
                own isNotNil and: [ own isWorldMorph ]]
    

    Querying classes and interfaces

    The development tools in Pharo (system browser, debugger, inspector...) all use the reflective features we have seen so far.

    Here are a few other messages that might be useful to build development tools:

    isKindOf: aClass returns true if the receiver is instance of aClass or of one of its superclasses. For instance:

    1.5 class
    >>> BoxedFloat64
    
    1.5 isKindOf: Float
    >>> true
    
    1.5 isKindOf: Number
    >>> true
    
    1.5 isKindOf: Integer
    >>> false
    

    respondsTo: aSymbol returns true if the receiver has a method whose selector is aSymbol. For instance:

    1.5 respondsTo: #floor
    >>> true "since Number implements floor"
    
    1.5 floor
    >>> 1
    
    Exception respondsTo: #,
    >>> true "exception classes can be grouped"
    

    Important

    Caveat: Although these features are especially useful for implementing development tools, they are normally not appropriate for typical applications. Asking an object for its class, or querying it to discover which messages it understands, are typical signs of design problems, since they violate the principle of encapsulation. Development tools, however, are not normal applications, since their domain is that of software itself. As such these tools have a right to dig deep into the internal details of code.

    Code metrics

    Let’s see how we can use Pharo’s introspection features to quickly extract some code metrics. Code metrics measure such aspects as the depth of the inheritance hierarchy, the number of direct or indirect subclasses, the number of methods or of instance variables in each class, or the number of locally defined methods or instance variables. Here are a few metrics for the class Morph, which is the superclass of all graphical objects in Pharo, revealing that it is a huge class, and that it is at the root of a huge hierarchy. Maybe it needs some refactoring!

    "inheritance depth"
    Morph allSuperclasses size.
    >>> 2
    
    "number of methods"
    Morph allSelectors size.
    >>> 1304
    
    "number of instance variables"
    Morph allInstVarNames size.
    >>> 6
    
    "number of new methods"
    Morph selectors size.
    >>> 896
    
    "number of new variables"
    Morph instVarNames size.
    >>> 6
    
    "direct subclasses"
    Morph subclasses size.
    >>> 63
    
    "total subclasses"
    Morph allSubclasses size.
    >>> 376
    
    "total lines of code!"
    Morph linesOfCode.
    >>> 4964
    

    One of the most interesting metrics in the domain of object-oriented languages is the number of methods that extend methods inherited from the superclass. This informs us about the relation between the class and its superclasses. In the next sections we will see how to exploit our knowledge of the runtime structure to answer such questions.


    This page titled 16.2: Introspection 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.