How to Make Block Local Variables the Default in Ruby 1.9

How to make block local variables the default in ruby 1.9?

The solution I am choosing is based on bobbywilson0's idea. Here is how it works:

x = 99
y = 98

scope { |x, y|
x = 20
y = 30
}

x #=> 99
y #=> 98

This is useful as the variables used in the scope are created at the start of the scope and do not close over any variables defined outside it, they are also GC'd at the end of the scope.

Here is the implementation:

def scope(&block)
num_required = block.arity >= 0 ? block.arity : ~block.arity
yield *([nil] * num_required)
end

This solution also takes default values into account making it functionally equivalent to a let* in lisp.

scope { |x = 20, z = (x * 3)| 
x #=> 20
z #=> 60
}

I blogged on it here: http://banisterfiend.wordpress.com/2010/01/07/controlling-object-scope-in-ruby-1-9/

Syntax for Block Local Variables

Choice B is not valid. As @matt indicated - it is a valid (though obscure) syntax (see here: How to write an inline block to contain local variable scope in Ruby?)

Choice C gives a default value to w, which is a regular value, while Choice D is a syntax for default keyword argument.

What is the purpose of setting Ruby block local variables when blocks have their own scope already?

Block scopes nest inside their lexically enclosing scope:

foo = :outerfoo
bar = :outerbar

1.times do |;bar|
foo = :innerfoo
bar = :innerbar
baz = :innerbaz
end

foo #=> :innerfoo
bar #=> :outerbar
baz # NameError

You need a way to tell Ruby: "I don't want this variable from the outer scope, I want a fresh one." That's what block local variables do.

Do block-local variables exist just to enhance readability?

The block parameter is a real parameter, while a block local variable is not.

If you give yield two parameters like this:

def foo
yield("hello", "world")
end

Calling

x = 10
foo do |y; x|
puts x
end

x is nil inside the function because only the first argument is assigned to y, the second argument is discarded.

Calling

x = 10
foo do |y, x|
puts x
end
#=>world

x gets the parameter correctly as "world".

Ruby Blocks, Procs and Local Variables

The Proc.new call creates a closure for the block that it's given. In creating a closure for the block, the block is bound to the original variables in the scope of the Proc.new call.

Why is this done?

It allows Ruby blocks to function as closures. Closures are extremely useful, and the Wikipedia entry (linked above) does an excellent job of explaining some of their applications.

How is this done?

This is done in the Ruby VM (in C code) by copying the Ruby control frame that exists before entering the Proc.new method. The block is then run in the context of this control frame. This effectively copies all of the bindings that are present in this frame. In Ruby 1.8, you can find the code for this in the proc_alloc function in eval.c. In Ruby 1.9, you can find this in the proc_new function in proc.c.

Ruby struct creation block cannot access variable outside the block

It's because def keyword starts new local variables scope, so default local variable isn't visible inside of it. The workaround is to use define_method, because the block you pass into it is closure:

default = 'test'
A = Struct.new(:a, :b) do
define_method(:initialize) do |*args|
super(*args)
self.b ||= default
end
end
a = A.new
a.b
# => "test"

Ruby 1.9.2: How to change scope/binding of a block

Looks like a GMail issue to me. Inside the blocks, self will be some object from the GMail gem so that you can have to, from, and similar DSL niceties available. You should be able to put self.trial_email into a local variable and then access that inside the blocks:

email_address = self.trial_email
@email = ::Gmail.connect!(gmail_name, gmail_password) do |gmail|
email = gmail.compose do
to 'trial@domain.com'
from email_address
subject email_address
#...


Related Topics



Leave a reply



Submit