Changing the binding of a block - ruby

I am trying to write a class that can capture a block of code and subsequently change the variables in the scope/closure.
class BlockCapture
attr_reader :block
def initialize(&block)
#block = block
#scope_additions = {}
end
def merge_into_scope(values = {})
#scope_additions = #scope_additions.merge(values)
end
def mutated_block
# some code that merges the scope additions into the binding
# and returns the context
end
end
captured_block = BlockCapture.new do
future_variable.upcase
end
captured_block.block.call # This should trigger a NameError, as `future_variable` is not in scope.
captured_block.merge_into_scope(future_variable: "I was added to the scope")
captured_block.mutated_block.call # This should work
=> "I WAS ADDED TO THE SCOPE"
I don't know how to go about merging the variables into the scope.
I have tried calling the binding method on the block variable, but it returns a new binding each time, so it appears to be a copy of rather than a reference to the binding of the original block.
irb(main):078:0> capture.block.binding
=> #<Binding:0x007fa38292e398>
irb(main):079:0> capture.block.binding
=> #<Binding:0x007fa382925f18>
irb(main):080:0> capture.block.binding
=> #<Binding:0x007fa38291d908>

Positing as an answer, even if I am not sure it would be helpful, because there is no space in the comment. Looks like you can run eval with the specific binding, for example with the current binding, created after the lambda was defined:
bind_lambda = -> (bind, x) { bind.eval x }
#=> #<Proc:0x007fec943b85e8#(pry):1 (lambda)>
First try ends with an error, as expected:
bind_lambda.call binding, 'future_var'
NameError: undefined local variable or method `future_var' for main:Object
from (pry):2:in `__pry__'
But after declaration of the local variable:
future_var = "here be dragons"
#=> "here be dragons"
We can reach it:
bind_lambda.call binding, 'future_var'
#=> "here be dragons"
This is because binding is the current environment.

Related

Ruby local variable scope

I'm struggling with variable scope in ruby. I was under the impression that local variables were accessible by methods below them. Looking at the following code however I'm getting an undefined variable error.
a = Array.new
def testing()
a.push("test")
end
testing
Using global variables it works just fine, how can I avoid using global variables?
There isn't much to say here except that local variables in Ruby are only accessible in the scope in which they are defined and any blocks (closures) defined in that scope that capture them. Since in Ruby, unlike some other dynamic languages, method are not closures, they do not capture local variables.
If you tried, say, this:
irb(main):001:0> a = 3
=> 3
irb(main):002:0> define_method(:testing) do
irb(main):003:1* puts a
irb(main):004:1> end
=> :testing
irb(main):005:0> testing
3
It works, since the code is in a block instead of a method.
Defining a method in the top-level can be quite confusing. Let's wrap your code in a class instead:
class Foo
a = []
def testing
a << 'test'
end
end
(I've shortened Array.new to [] and a.push(...) to a << ...)
Foo#testing can be called via:
foo = Foo.new
foo.testing
#=> undefined local variable or method `a'
Apparently, this doesn't work. The first a is a local variable in the scope of the class body, whereas the second a is a local variable within an instance method.
Moving the variable initialization out of the class body into the initialize method doesn't work either, because local variables are not shared across methods:
class Foo
def initialize
a = [] # <- one 'a'
end
def testing
a << 'test' # <- another 'a'
end
end
To get this working, you have to use an instance variable:
class Foo
def initialize
#a = []
end
def testing
#a << 'test'
end
end
foo = Foo.new
foo.testing
#=> ["test"]
foo.testing
#=> ["test", "test"]
You could use instance variables. Any variable whose name begins with # is an instance variable and is available anywhere in the class or method in which it is defined. For example, the variable #A defined within class B will be available to any methods in B.
2.3.3 :007 > def testing()
2.3.3 :008?> [].push("test")
2.3.3 :009?> end
=> :testing
2.3.3 :010 > testing
=> ["test"]
You can't let local variables accessible by methods below them , you can use block like the answer by #Linuxios, or use the way that it easy work.

# symbol before var name in Ruby script

I'm not entirely sure how to Google this question, so I come here with an example. I'm looking at a selenium script that has an excerpt like the following:
def setup
#starting_url = "https://www.example.com"
#restricted_url = "https://www.example.com/restricted"
#user_email = "foo#bar.com"
#user_password = "notarealpassword"
#headless_mode = false
#page_timeout = 15 # seconds
#log_file = 'log/development.log'
#lineup_file = 'data/lineup1.csv'
... more code
end
My question is, why does every variable here get prefixed with an # symbol? This method is not part of a class. It is being written in the global scope. I understand the variables have significance in with an # symbol in the case of an explicit class, but what about here?
Those variables become instance variables in the scope of the main object. They will be available inside other methods also defined at the global scope. Local variables defined within methods at the global scope would go out of scope as soon as the method returned.
Illustration:
def foo
lvar = 1
#ivar = 2
end
def bar
puts #ivar # will print 2 after foo() is called
puts lvar # will throw NameError
end
#ivar # => nil
foo # initializes #ivar
#ivar # => 2
bar # prints 2, throws NameError
lvar # throws NameError

Scopes and blocks

In ruby, module, class, and def keywords define a new scope. I'm confused as to why local variables defined in a block do not exist outside of the block. Is a block argument another scope gate? For example:
(1..2).each { |n| numer = 'czesc' }
numer # => NameError: undefined local variable or method `czesc' for main:Object
Or more simply:
def kon; end
kon { kot = 3 }
kot # => NameError: undefined local variable or method `kot' for main:Object
I thought, maybe it's not persisted because it's defined in the method's arguments, but the following works for the normal arguments:
def pokaz(cos)
p cos
end
pokaz(co = "to")
co # => "to"
You might think about codeblocks as of lazy instances of Proc class.
Your second example with kon/kot does actually not what you were expecting from it. You supply a codeblock to a function kon. This codeblock is not evaluated unless requested. In your snippet it is never evaluated. Look:
▶ def kon; end
#⇒ :kon
▶ kon { puts 'I am here' }
#⇒ nil
You passed a codeblock. Fine. Now kon should invoke it, if needed:
▶ def kon; yield ; end
#⇒ :kon
▶ kon { puts 'I am here' }
# I am here
#⇒ nil
When you passed a codeblock to, say, each method of Enumerator instance:
(1..2).each { |n| numer = 'czesc' }
The codeblock is being evaluated in context of this Enumerator. The default receiver, self, is still the main thread, though. That makes codeblocks to act mostly like closures (they have an access to the caller bindings):
▶ kot = 10
#⇒ 10
▶ 5.times do |i|
▷ kot = i
▷ end
▶ puts kot
#⇒ 4
Hope it sheds some light.
In ruby, module, class, and def keywords define a new scope.
There are six scope constructs in Ruby: module bodies, class bodies, method bodies and script bodies create new scopes, block bodies and "stabby lambda" literal bodies create new nested scopes.
I'm confused as to why local variables defined in a block do not exist outside of the block.
Because a block body has its own lexical scope. The scope is nested, which means that it has access to local variables from the outer scope, but variables from an inner scope never leak into the outer scope.

Create blank binding in the scope of an object

class Foo
def self.run(n,code)
foo = self.new(n)
#env = foo.instance_eval{ binding }
#env.eval(code)
end
def initialize(n)
#n = n
end
end
Foo.run( 42, "p #n, defined? foo" )
#=> 42
#=> "local-variable"
The sample program above is intended to evaluate arbitrary code within the scope of a Foo instance. It does that, but the binding is "polluted" with the local variables from the code method. I don't want foo, n, or code to be visible to the eval'd code. The desired output is:
#=> 42
#=> nil
How can I create a binding that is (a) in the scope of the object instance, but (b) devoid of any local variables?
The reason that I am creating a binding instead of just using instance_eval(code) is that in the real usage I need to keep the binding around for later usage, to preserve the local variables created in it.
so like this? or did i miss something important here?
class Foo
attr_reader :b
def initialize(n)
#n = n
#b = binding
end
def self.run(n, code)
foo = self.new(n)
foo.b.eval(code)
end
end
Foo.run(42, "p #n, defined?(foo)")
# 42
# nil
or move it further down to have even less context
class Foo
def initialize(n)
#n = n
end
def b
#b ||= binding
end
def self.run(n, code)
foo = self.new(n)
foo.b.eval(code)
end
end
Foo.run(42, "p #n, defined?(foo), defined?(n)")
# 42
# nil
# nil
Answer:
module BlankBinding
def self.for(object)
#object = object
create
end
def self.create
#object.instance_eval{ binding }
end
end
Description:
In order to get a binding with no local variables, you must call binding in a scope without any of them. Calling a method resets the local variables, so we need to do that. However, if we do something like this:
def blank_binding_for(obj)
obj.instance_eval{ binding }
end
…the resulting binding will have an obj local variable. You can hide this fact like so:
def blank_binding_for(_)
_.instance_eval{ binding }.tap{ |b| b.eval("_=nil") }
end
…but this only removes the value of the local variable. (There is no remove_local_variable method in Ruby currently.) This is sufficient if you are going to use the binding in a place like IRB or ripl where the _ variable is set after every evaluation, and thus will run over your shadow.
However, as shown in the answer at top, there's another way to pass a value to a method, and that's through an instance variable (or class variable, or global variable). Since we are using instance_eval to shift the self to our object, any instance variables we create in order to invoke the method will not be available in the binding.

Why do instance variables seemingly disappear when inside a block?

Forgive me, guys. I am at best a novice when it comes to Ruby. I'm just curious to know the explanation for what seems like pretty odd behavior to me.
I'm using the Savon library to interact with a SOAP service in my Ruby app. What I noticed is that the following code (in a class I've written to handle this interaction) seems to pass empty values where I expect the values of member fields to go:
create_session_response = client.request "createSession" do
soap.body = {
:user => #user, # This ends up being empty in the SOAP request,
:pass => #pass # as does this.
}
end
This is despite the fact that both #user and #pass have been initialized as non-empty strings.
When I change the code to use locals instead, it works the way I expect:
user = #user
pass = #pass
create_session_response = client.request "createSession" do
soap.body = {
:user => user, # Now this has the value I expect in the SOAP request,
:pass => pass # and this does too.
}
end
I'm guessing this strange (to me) behavior must have something to do with the fact that I'm inside a block; but really, I have no clue. Could someone enlighten me on this one?
First off, #user is not a "private variable" in Ruby; it is an instance variable. Instance variables are available within the the scope of the current object (what self refers to). I have edited the title of your question to more accurately reflect your question.
A block is like a function, a set of code to be executed at a later date. Often that block will be executed in the scope where the block was defined, but it is also possible to evaluate the block in another context:
class Foo
def initialize( bar )
# Save the value as an instance variable
#bar = bar
end
def unchanged1
yield if block_given? # call the block with its original scope
end
def unchanged2( &block )
block.call # another way to do it
end
def changeself( &block )
# run the block in the scope of self
self.instance_eval &block
end
end
#bar = 17
f = Foo.new( 42 )
f.unchanged1{ p #bar } #=> 17
f.unchanged2{ p #bar } #=> 17
f.changeself{ p #bar } #=> 42
So either you are defining the block outside the scope where #user is set, or else the implementation of client.request causes the block to be evaluated in another scope later on. You could find out by writing:
client.request("createSession"){ p [self.class,self] }
to gain some insight into what sort of object is the current self in your block.
The reason they "disappear" in your case—instead of throwing an error—is that Ruby permissively allows you to ask for the value of any instance variable, even if the value has never been set for the current object. If the variable has never been set, you'll just get back nil (and a warning, if you have them enabled):
$ ruby -e "p #foo"
nil
$ ruby -we "p #foo"
-e:1: warning: instance variable #foo not initialized
nil
As you found, blocks are also closures. This means that when they run they have access to local variables defined in the same scope as the block is defined. This is why your second set of code worked as desired. Closures are one excellent way to latch onto a value for use later on, for example in a callback.
Continuing the code example above, you can see that the local variable is available regardless of the scope in which the block is evaluated, and takes precedence over same-named methods in that scope (unless you provide an explicit receiver):
class Foo
def x
123
end
end
x = 99
f.changeself{ p x } #=> 99
f.unchanged1{ p x } #=> 99
f.changeself{ p self.x } #=> 123
f.unchanged1{ p self.x } #=> Error: undefined method `x' for main:Object
From the documentation:
Savon::Client.new accepts a block inside which you can access local variables and even public methods from your own class, but instance variables won’t work. If you want to know why that is, I’d recommend reading about instance_eval with delegation.
Possibly not as well documented when this question was asked.
In the first case, self evaluates to client.request('createSession'), which doesn't have these instance variables.
In the second, the variables are brought into the block as part of the closure.
Another way to fix the issue would be to carry a reference to your object into the block rather than enumerating each needed attribute more than once:
o = self
create_session_response = client.request "createSession" do
soap.body = {
:user => o.user,
:pass => o.pass
}
end
But now you need attribute accessors.

Resources