Ruby: Scope and Closures

Ruby: Scope and Closures

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 the object's methods and attributes.

Suppose we have this code:

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.

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 different 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:

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 
# (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:

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

要查看或添加评论,请登录

Robert Rodes的更多文章

社区洞察

其他会员也浏览了