Skip to main content
Engineering LibreTexts

13.2: Variables and Blocks

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

    A block can have its own temporary variables. Such variables are initialized during each block execution and are local to the block. We will see later how such variables are kept. Now the question we want to make clear is what is happening when a block refers to other (non-local) variables. A block will close over the external variables it uses. It means that even if the block is executed later in an environment that does not lexically contain the variables used by a block, the block will still have access to the variables during its execution. Later, we will present how local variables are implemented and stored using contexts.

    In Pharo, private variables (such as self, instance variables, method temporaries and arguments) are lexically scoped: an expression in a method can access to the variables visible from that method, but the same expression put in another method or class cannot access the same variables because they are not in the scope of the expression (i.e., visible from the expression).

    At runtime, the variables that a block can access, are bound (get a value associated to them) in the context in which the block that contains them is defined, rather than the context in which the block is evaluated. It means that a block, when evaluated somewhere else can access variables that were in its scope (visible to the block) when the block was created. Traditionally, the context in which a block is defined is named the block home context.

    The block home context represents a particular point of execution (since this is a program execution that created the block in the first place), therefore this notion of block home context is represented by an object that represents program execution: a context object in Pharo. In essence, a context (called stack frame or activation record in other languages) represents information about the current evaluation step such as the context from which the current one is executed, the next byte code to be executed, and temporary variable values. A context is a Pharo execution stack element. This is important and we will come back later to this concept.

    A block is created inside a context (an object that represents a point in the execution).

    Some little experiments

    Let’s experiment a bit to understand how variables are bound in a block. Define a class named Bexp (for BlockExperiment):

    Object subclass: #Bexp
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'BlockExperiment'
    

    Experiment 1: Variable lookup. A variable is looked up in the block definition context. We define two methods: one that defines a variable t and sets it to 42 and a block [t traceCr] and one that defines a new variable with the same name and executes a block defined elsewhere.

    Bexp>>setVariableAndDefineBlock
        |t|
        t := 42.
        self evaluateBlock: [ t traceCr ]
    
    Bexp>>evaluateBlock: aBlock
        |t|
        t := nil.
        aBlock value
    
    Bexp new setVariableAndDefineBlock
        → 42
    

    Executing the Bexp new setVariableAndDefineBlock expression prints 42 in the Transcript (message traceCr). The value of the temporary variable t defined in the setVariableAndDefineBlock method is the one used rather than the one defined inside the method evaluateBlock: even if the block is evaluated during the execution of this method. The variable t is looked up in the context of the block creation (context created during the execution of the method setVariableAndDefineBlock and not in the context of the block evaluation (method evaluateBlock:).

    Let’s look at it in detail. Figure \(\PageIndex{1}\) shows the execution of the expression Bexp new setVariableAndDefineBlock.

    • During the execution of method setVariableAndDefineBlock, a variable t is defined and it is assigned 42. Then a block is created and this block refers to the method activation context - which holds temporary variables (Step 1).
    • The method evaluateBlock: defines its own local variable t with the same name than the one in the block. This is not this variable, however, that is used when the block is evaluated. While executing the method evaluateBlock: the block is evaluated (Step 2), during the execution of the expression t traceCr the non-local variable t is looked up in the home context of the block i.e., the method context that created the block and not the context of the currently executed method.
    Non-local variables are looked up the method activation context.
    Figure \(\PageIndex{1}\): Non-local variables are looked up the method activation context where the block was created and not where it is evaluated.

    Non-local variables are looked up in the home context of the block (i.e., the method context that created the block) and not the context executing the block.

    Experiment 2: Changing a variable value. Let’s continue our experiments. The method setVariableAndDefineBlock2 shows that a non-local variable value can be changed during the evaluation of a block. Executing Bexp new setVariableAndDefineBlock2 prints 33, since 33 is the last value of the variable t.

    Bexp>>setVariableAndDefineBlock2
        |t|
        t := 42.
        self evaluateBlock: [ t := 33. t traceCr ]
    
    Bexp new setVariableAndDefineBlock2
        → 33
    

    Experiment 3: Accessing a shared non-local variable. Two blocks can share a non-local variable and they can modify the value of this variable at different moments. To see this, let us define a new method setVariableAndDefineBlock3 as follows:

    Bexp>>setVariableAndDefineBlock3
        |t|
        t := 42.
        self evaluateBlock: [ t traceCr. t := 33. t traceCr ].
        self evaluateBlock: [ t traceCr. t := 66. t traceCr ].
        self evaluateBlock: [ t traceCr ]
    
    Bexp new setVariableAndDefineBlock3
        → 42
        → 33
        → 33
        → 66
        → 66
    

    Bexp new setVariableAndDefineBlock3 will print 42, 33, 33, 66 and 66. Here the two blocks [ t := 33. t traceCr ] and [ t := 66. t traceCr ] access the same variable t and can modify it. During the first execution of the method evaluateBlock: its current value 42 is printed, then the value is changed and printed. A similar situation occurs with the second call. This example shows that blocks share the location where variables are stored and also that a block does not copy the value of a captured variable. It just refers to the location of the variables and several blocks can refer to the same location.

    Experiment 4: Variable lookup is done at execution time. The following example shows that the value of the variable is looked up at runtime and not copied during the block creation. First add the instance variable block to the class Bexp.

    Object subclass: #Bexp
        instanceVariableNames: 'block'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'BlockExperiment'
    

    Here the initial value of the variable t is 42. The block is created and stored into the instance variable block but the value to t is changed to 69 before the block is evaluated. And this is the last value (69) that is effectively printed because it is looked up at execution-time. Executing Bexp new setVariableAndDefineBlock4 prints 69.

    Bexp>>setVariableAndDefineBlock4
        |t|
        t := 42.
        block := [ t traceCr: t ].
        t := 69.
        self evaluateBlock: block
    
    Bexp new setVariableAndDefineBlock4
        → 69.
    

    Experiment 5: For method arguments. We can expect that method arguments are bound in the context of the defining method. Let’s illustrate this point now. Define the following methods.

    Bexp>>testArg
        self testArg: 'foo'.
    
    Bexp>>testArg: arg
        block := [arg traceCr].
        self evaluateBlockAndIgnoreArgument: 'zork'.
    
    Bexp>>evaluateBlockAndIgnoreArgument: arg
        block value.
    

    Now executing Bexp new testArg: 'foo' prints 'foo' even if in the method evaluateBlockAndIgnoreArgument: the temporary arg is redefined. In fact each method invocation has its own values for the arguments.

    Experiment 6: self binding. Now we may wonder if self is also captured. To test we need another class. Let’s simply define a new class and a couple of methods. Add the instance variable x to the class Bexp and define the initialize method as follows:

    Object subclass: #Bexp
        instanceVariableNames: 'block x'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'BlockExperiment'
    
    Bexp>>initialize
        super initialize.
        x := 123.
    

    Define another class named Bexp2.

    Object subclass: #Bexp2
        instanceVariableNames: 'x'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'BlockExperiment'
    
    Bexp2>>initialize
        super initialize.
        x := 69.
    
    Bexp2>>evaluateBlock: aBlock
        aBlock value 
    

    Then define the methods that will invoke methods defined in Bexp2.

    Bexp>>evaluateBlock: aBlock
        Bexp2 new evaluateBlock: aBlock
    
    Bexp>>evaluateBlock
        self evaluateBlock: [self crTrace ; traceCr: x]
    
    Bexp new evaluateBlock
        → a Bexp123 "and not a Bexp269"
    

    Now when we execute Bexp new evaluateBlock, we get a Bexp123 printed in the Transcript showing that a block captures self too, since an instance of Bexp2 executed the block but the printed object (self) is the original Bexp instance that was accessible at the block creation time.

    Conclusion. We show that blocks capture variables that are reached from the context in which the block was defined and not where there are executed. Blocks keep references to variable locations that can be shared between multiple blocks.

    Block-local variables

    As we saw previously a block is a lexical closure that is connected to the place where it is defined. In the following, we will illustrate this connection by showing that block local variables are allocated in the execution context link to their creation. We will show the difference when a variable is local to a block or to a method (see Figure \(\PageIndex{2}\)).

    Block allocation. Implement the following method blockLocalTemp.

    Bexp>>blockLocalTemp
        | collection |
        collection := OrderedCollection new.
        #(1 2 3) do: [ :index |
            | temp |
            temp := index.
            collection add: [ temp ] ].
        ^ collection collect: [ :each | each value ]
    

    Let’s comment the code: we create a loop that stores the current index (an block argument) in a temporary variable temp created in the loop. We then store a block that accesses this variable in a collection. After the loop, we execute each accessing block and return the collection of values. If we execute this method, we get a collection with 1, 2 and 3. This result shows that each block in the collection refers to a different temp variable. This is due to the fact that an execution context is created for each block creation (at each loop step) and that the block [ temp ] is stored in this context.

    blockLocalTemp vs. blockOutsideTemp execution.
    Figure \(\PageIndex{2}\): blockLocalTemp execution (Left) - blockOutsideTemp execution (Right)

    Method allocation. Now let us create a new method that is the same as blockLocalTemp except that the variable temp is a method variable instead of a block variable.

    Bexp>>blockOutsideTemp
        | collection temp |
        collection := OrderedCollection new.
        #(1 2 3) do: [ :index |
            temp := index.
            collection add: [ temp ] ].
        ^ collection collect: [ :each | each value ]
    

    When we execute the method blockOutsideTemp, we now get a collection with 3, 3 and 3. This result shows that each block in the collection now refers to a single variable temp allocated in the blockOutsideTemp context leading to the fact that temp is shared by the blocks.


    This page titled 13.2: Variables and Blocks is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.