Scope
An entity’s scope is the area of the application in which it is “visible.” For example, a variable’s scope is the area of an application that can access the variable. And an object’s scope is the area of an application that can access its methods and attributes.
Suppose we have this code:
1 2 3 4 5 6 7 8 9 |
test_var = 'This is the variable in the main area' def test_method(test_arg) test_local_var = 'This is the local variable' end test_method('This is the argument variable') |
Now, let’s see what we can see from where. I’ll attempt to print each variable, both from inside and outside the method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
test_var = 'This is the variable in the main area' def test_method(test_arg) test_local_var = 'This is the local variable' puts defined?(test_arg) ? test_arg : 'test_arg is not in scope in test_method' puts defined?(test_local_var) ? test_local_var : 'test_local_var is not in scope in test_method' puts defined?(test_var) ? test_var : 'test_var is not in scope in test_method' end test_method('This is the argument variable') puts defined?(test_arg) ? test_arg : 'test_arg is not in scope in main' puts defined?(test_local_var) ? test_local_var : 'test_local_var is not in scope in main' puts defined?(test_var) ? test_var : 'test_var is not in scope in main' #=> This is the argument variable #=> This is the local variable #=> test_var is not in scope in test_method #=> test_arg is not in scope in main #=> test_local_var is not in scope in main #=> This is the variable in the main area |
From this it is clear that the main area can’t see inside the method, and the the method can’t see outside itself into the main area. Therefore, the main area and the method have separate scopes.
Scope and Classes
A class can’t see outside of itself, but a class’s methods and attributes are public, or visible to whatever context instantiates it. Unless otherwise specified, that is: while a class’s methods and attributes are public by default, any of them can be marked private
. If they are, then they are only visible inside the class.
Here’s some code to show this. Everything that follows the private
keyword is private; the rest is public:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class ScopeTest attr_accessor :instance_var def initialize @instance_var = 'Instance variable' end def scope_method() puts 'Scope method' end def show_private private_method end private def private_method puts 'private method' end end test = ScopeTest.new() puts test.instance_var test.scope_method test.show_private test.private_method #=> Instance variable #=> Scope method #=> private method #=> private method `private_method' called for #<ScopeTest:0x000000010a9db858 @instance_var="Instance variable"> (NoMethodError) |
An attempt to call #private_method
directly results in a NoMethodError
. But a call to #show_private
runs without an error. This is because #show_private
is public, so we can call it directly. Then #show_private
can call #private_method
directly, because they are both inside the same class.
We can describe a class’s public scope as out can see in, but in can’t see out.
Closures
A closure is a chunk of code that “encloses” around itself everything that is in scope in the context in which it is created. This scope is called the lexical scope. Everything that is visible in the lexical scope is called the closure’s state. So, we can describe a closure’s scope as the opposite of a class’s: in can see out, but out can’t see in.
When a closure is passed around in its application environment, its original state is passed around with it. All of that original state remains in scope even if it is not in the scope of wherever the closure got passed to.
In Ruby, blocks, procs and lambdas are closures, while methods are not.
Consider this method, which executes a block:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def test message = "No I can't!" yield message end message = 'You can see me!' test do |arg| puts message #=> You can see me! puts arg # => No I can't! end |
We first initialize and assign the message
variable in the main
context. Then we call the #test
method, specifying a block argument. We have initialized another message
variable inside the #test
method. We pass that variable to the block when we yield to it. That one is available, then, to the block as the arg
argument.
Since blocks are closures, this block can “see” the message
variable in main
. Furthermore, when the method yields to the block and the block executes, the block can still “see” the message
variable, even though the method itself can’t, because message
was in scope when we created the block.
In other words, the block carries with itself the state that is in scope where it was written — its lexical scope. So, one definition of a closure is a chunk of code that carries with it the state that is in its lexical scope, even though the method that invokes the closure might not have direct access to it.
Related Articles
This article is one of a series of four, in no particular order. Here are the other three:
Ruby: Blocks
Ruby: Procs, Lambdas and Bindings
Ruby: Block Parameters and Return Values