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}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)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 variablet
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 variablet
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 methodevaluateBlock:
the block is evaluated (Step 2), during the execution of the expressiont traceCr
the non-local variablet
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 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.

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.