Scopes and closures
@customers = Customer.all
Riif::IIF.new do |iif|
iif.cust do
@customers.each do |customer|
row do
name customer.full_name
end
end
end
end


Scope
Motivation, how does ruby resolve
- methods,
- instance variables,
- local variables,
- constants(out of inheritance tree)
class Vehicle
WHEELS = 4
end
class Car < Vehicle
puts WHEELS + 1
end
module Vehicle
WHEELS = 4
class Car
puts WHEELS + 1
end
end
Class: Vehicle
superclass: Object
constants: WHEELS
Class: Car
superclass: Vehicle
Module: Vehicle
superclass: Object
constants: WHEELS, Car
Class: Car
superclass: Object
struct RBasic {
unsigned long flags;
VALUE klass;
};
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
Dynamic
Lexical
g = 'global'
module Vehicle
m = 'module'
class Car
c = 'class'
def run
l = 'method'
blk = proc do
b = 'block'
nested_blk = proc do
nested_b = 'nested block'
end
end
end
end
end
g = 'global'
module Vehicle
m = 'module'
class Car
c = 'class'
def run
l = 'method'
binding.pry
blk = proc do
b = 'block'
end
end
end
end
- compiler
- remains the same forever
- Which local vars are accessible?
- debugger
- can change in runtime
- Who am I? (what is self)
Scope gates
- keywords: class, module, def
- each time we cross scope gates, previous local variables 'fall out of scope', i.e. become inaccessible
g = 'global'
module Vehicle # Scope gate
m = 'module'
class Car # Scope gate
c = 'class'
def run # Scope gate
l = 'method'
blk = proc do # NOT scope gate
b = 'block'
end
end
end
end
Flattening lexical scope
- replace keywords with their method alternatives
- module => Module.new
- class => Class.new
- def => define_method
g = 'global'
Vehicle = Module.new do
m = 'module'
Car = Class.new do
c = 'class'
define_method :run do
l = 'method'
blk = proc do
b = 'block'
end
end
end
end
g = 'global'
module Vehicle
m = 'module'
class Car
c = 'class'
def run
l = 'method'
blk = proc do
b = 'block'
end
end
end
end
Blocks are closures
def africa
tree = 'triangle'
block = proc do
animal = 'lion'
tree # => "triangle"
end
animal # => ???
end
africa
- intoroduces new (private) scope with local vars that can't be accessed from outside
- opposed to scope gates, block only opens new scope, but does not close previous
- Coincidence? closure since it can access surrounding/enclosing scope

Closures
- functions that remember context in which they were created

def counter(start = 0, offset = 1)
current = start
read = proc { current }
increment = proc { current += offset }
return read, increment
end
t_get, t_inc = counter(2, 2)
3.times(&t_inc)
t_get.call # => 8
s_get, s_inc = counter(10, 7)
s_inc.call
s_get.call # => 17
Dynamic context change
- evaluates a block in a context of a receiver
-
receiver becomes self in passed block
-
blocks lexical scope remains untouched
-
extensive usage in DSL codes bases
def identity
c = '~'
proc do
puts "closure: #{c}"
puts "self: #{self}"
end
end
id = identity
'Rails'.instance_eval(&id)
# >> closure: ~
# >> self: Rails
1.234.instance_eval(&id)
# >> closure: ~
# >> self: 1.234
add = proc do |other|
self + other
end
1.instance_exec(2, &add) # => 3
'C'.instance_exec('@', &add) # => "Cat"
Case study
@customers = Customer.all
Riif::IIF.new do |iif|
iif.cust do
@customers.each do |customer|
row do
name customer.full_name
end
end
end
end
Again, with context
class Reporter # :nodoc:
attr_reader :customers
def initialize(customers)
@customers = customers
end
def call
Riif::IIF.new do |iif|
iif.cust do
@customers.each do |customer|
row do
name customer.full_name
end
end
end
end
end
def self.build(customers)
new(customers).call
end
end
Using closure
class Reporter # :nodoc:
attr_reader :customers
def initialize(customers)
@customers = customers
end
def call
all_customers = @customers
Riif::IIF.new do |iif|
iif.cust do
all_customers.each do |customer|
row do
name customer.full_name
end
end
end
end
end
def self.build(customers)
new(customers).call
end
end
Using higher order function
class Reporter # :nodoc:
attr_reader :customers
def initialize(customers)
@customers = customers
end
def call
Riif::IIF.new do |iif|
iif.cust(&customer_row_generator)
end
end
def customer_row_generator
ctx = self
proc do
ctx.customers.each do |customer|
row do
name customer.full_name
end
end
end
end
def self.build(customers)
new(customers).call
end
end
Questions?
Scopes and closures
By vrabac
Scopes and closures
- 447