assigning variables in Ruby blocks to higher scope - ruby

I'm trying to use the retriable gem.
I have a function that returns a value that's wrapped inside a retriable block like below.
Retriable.retriable do
x = some_function(1, 2)
end
....do something with x...
The only way I've figured out how to do this is by using a global variable i.e #x inside the block but I don't think that's necessarily the best way. I would appreciate it if someone could point me to a better way of doing this.
Thanks

In the general case, you solve this by declaring the variable outside the loop:
x = nil
%w[foo bar baz].each do |word|
x = word
end
puts x # => "baz"
See it in action here: https://repl.it/#jrunning/AbsoluteShamelessVerification

This exact case can be solved like this:
x = Retriable.retriable do
some_function(1, 2)
end
But in general, no, you can't declare variables in outer scopes. That's why we have scopes.

You should be able to assign a value to a new variable by whatever the block returns so:
x = Retriable.retriable do
some_function(1, 2)
end

Related

Changing the object identity of a formal parameter

I will try to explain the problem with a simple example:
def enclose(x)
[x]
end
In my application, enclose does something more complex, but in essence it returns an array, the content of which is solely determined by the value of the parameter x. I could it use it like this:
foo = 'abcd'
....
foo = enclose(foo)
Now to my question: Is it possible to write a method enclose!, which simply replaces the parameter by its enclosed version, so that the example could be written as
foo = 'abcd'
....
enclose!(foo)
Since Ruby passes arguments by reference, I thought hat this could maybe be possible. The naive approach,
def enclose!(x)
x = [x]
end
does not work - I think this is because the assignment creates a new object and leaves the actual parameter untouched.
Is there way, that I can achieve my goal? I think in Smallalk, there would be a method become which would change the object identity, but I didn't find something similar in Ruby.
Since Ruby passes arguments by reference, I thought hat this could maybe be possible.
Ruby is pass-by-value, not pass-by-reference, which you have proven yourself, because otherwise your code would have worked.
I think in Smallalk, there would be a method become which would change the object identity, but I didn't find something similar in Ruby.
There isn't. Ruby has neither pass-by-reference nor become:, what you want simply isn't possible.
There's some other interesting posts about how ruby is pass by value, but the values are references.
What it boils down to is, you can modify the variable an object refers to, but you cannot change it to refer to another object.
> a = [1]
=> [1]
> def add_a(array)
> array << "a"
> end
=> :add_a
> add_a a
=> [1, "a"]
> a
=> [1, "a"]
There is a way to sort of accomplish what you are asking for but it's not quite pretty. Ruby has this concept of a binding (http://ruby-doc.org/core-2.2.0/Binding.html), which is like a CallContext in .NET.
You can do something like this:
def enclose(x)
[x]
end
def enclose!(x, binding)
eval("#{x} = [#{x}]", binding)
end
foo = 'abcd'
enclose!(:foo, binding)
=> ["abcd"]
In the script above, the :foo means you are passing the name of the variable, and the binding (context) where to find its value. Then you're dynamically calling eval to evaluate the assignment operation foo = [foo].

Plus equals with ruby send message

I'm getting familiar with ruby send method, but for some reason, I can't do something like this
a = 4
a.send(:+=, 1)
For some reason this doesn't work. Then I tried something like
a.send(:=, a.send(:+, 1))
But this doesn't work too. What is the proper way to fire plus equals through 'send'?
I think the basic option is only:
a = a.send(:+, 1)
That is because send is for messages to objects. Assignment modifies a variable, not an object.
It is possible to assign direct to variables with some meta-programming, but the code is convoluted, so far the best I can find is:
a = 1
var_name = :a
eval "#{var_name} = #{var_name}.send(:+, 1)"
puts a # 2
Or using instance variables:
#a = 2
var_name = :#a
instance_variable_set( var_name, instance_variable_get( var_name ).send(:+, 1) )
puts #a # 3
See the below :
p 4.respond_to?(:"+=") # false
p 4.respond_to?(:"=") # false
p 4.respond_to?(:"+") # true
a+=1 is syntactic sugar of a = a+1. But there is no direct method +=. = is an assignment operator,not the method as well. On the other hand Object#send takes method name as its argument. Thus your code will not work,the way you are looking for.
It is because Ruby doesn't have = method. In Ruby = don't work like in C/C++ but it rather assign new object reference to variable, not assign new value to variable.
You can't call a method on a, because a is not an object, it's a variable, and variables aren't objects in Ruby. You are calling a method on 4, but 4 is not the thing you want to modify, a is. It's just not possible.
Note: it is certainly possible to define a method named = or += and call it, but of course those methods will only exist on objects, not variables.
class Fixnum
define_method(:'+=') do |n| self + n end
end
a = 4
a.send(:'+=', 1)
# => 5
a
# => 4
This might miss the mark a bit, but I was trying to do this where a is actually a method dynamically called on an object. For example, with attributes like added_count and updated_count for Importer I wrote the following
class Importer
attr_accessor :added_count, :updated_count
def increment(method)
send("#{method}=", (send(method) + 1))
end
end
So I could use importer.increment(:added_count) or importer.increment(:updated_count)
Now this may seem silly if you only have these 2 different counters but in some cases we have a half dozen or more counters and different conditions on which attr to increment so it can be handy.

Ruby: using strings to dynamically create objects

How do I use strings or symbols to create new variables || objects? Say I want 5 unique objects of an already made item class;
for x in 1..5
item_x = item.new() #where x is obviously the number value of the iterator
end
I have tried using eval() in this manner:
for x in 1..5
eval( "item_" << x << "= item.new()")
end
Hoping that putting the string I want executed into eval would have it executed as if I put it into the code.
I have searched for dynamic object creation and have not found anyone with this problem, sorry if this is mundane stuff. I have found references to people using .const_get and Openstruct but these don't seem to fix my problem in a manner I can understand.
Is there any reason you can't store your objects in an array? That would make them easier to both create and access:
items = []
5.times do
items << Item.new()
end
Then instead of item_1 through item_5, you have item[0] through item[4].
That said, if you really need/want to do the hard thing, instance_variable_set is an option if instance variables (with #) are okay:
for x in 1..5
self.instance_variable_set("#item_#{x}", Item.new())
end
I'd recommend just sticking with the array.
Update: From your comment, it sounds like your actual use case involves desired variable names that aren't consecutive. In that case, I'd use a Hash rather than an Array.
Think of it this way: whatever string you wish to use as the name of a local variable, just use it as a key in a local Hash. I can't think of any instance where that wouldn't be better than what you're trying, even if what you're trying worked.
You can use instance_variable_set to create instance variables in the manner you depict:
for x in 1..5
variable_name = "item_#{x}"
instance_variable_set("##{variable_name}", "value to assign to variable")
end
puts #item_1 #=> "value assigned to item_1"
Note, however, that this will not create local variables.

Use input name in return value variable name

I'm trying to make this simple method return a value related to the name of its input. For instance if I give the method "people_array" it should return "people_array_of_arrays."
If I were using the method in IRB I would get something like:
people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(people_array)
=> people_array_of_arrays
people_array
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
people_array_of_arrays
=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
I have written this so far, but have not been able to figure out how to return a nicely named array of arrays. All I could think of was string interpolation but that isn't exactly what I need.
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
#{array}_of_arrays = formatted_array
end
I saw there was a method variablize, but that returns an instance variable which isn't exactly what I want. Any pointers?
I do not think that it can be easily done. Suppose you were able to define a local variable in some way within the method definition. But the scope of that local variable is limited to the method definition. So the moment you go outside of the method definition, the local variable name is gone. So in order to do it, you have to somehow get the binding information of the environment outside of the method definition, and define a local variable within that. I do not know if that is possible.
With instance variables, things get a little easier using instance_variable_set, but I am not sure how to implement it fully. First of all, getting the name of the original variable is tricky.
And what you are trying to do is not the right approach. You should think of different ways.
I think the best you can do is to use an instance variable instead of a local variable, and also give the name of the variable explicitly instead of the array itself:
def make_array_of_arrays(variable_name)
array = instance_variable_get("##{variable_name}")
# Your code here
instance_variable_set("##{variable_name}_of_arrays", formatted_array)
end
#people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(:people_array)
#people_array_of_arrays
#=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
This also might be useful.
No need for meta-programming (unless I misunderstand your question). Simply return your formatted array:
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
formatted_array
end
Then you can assign the return value to whatever name you want:
people_array_of_arrays = make_array_of_arrays(people_array)
Note, you can use map to simplify your make_array_of_arrays method:
def make_array_of_arrays(array)
array.map do |feed|
feed.split("\t")
end
end
The big problem here is there's no good way to access the name of a variable.
Barring that and building on Sean Vieira's addition, you could do some eval magic to get this:
def make_array_of_arrays(array, array_name)
new_array = array.map { |feed| feed.split("\t") }
eval("def #{array_name}_of_arrays; return #{new_array}; end")
end
This basically creates a function for your *_of_arrays line that returns the array you're looking for.
If you could find a way to get the name of the variable, you'd have everything you want.
Not that I really officially endorse this method. And I can't for the life of me figure out why you'd want to do this. It's very unidiomatic, and will be confusing for anyone looking at that chunk of code.
This is not easy nor advisable.
Think of Ruby Objects as people (you and me) communicating by phone (phonenumbers being object_id's). Imagine I am in your list of phonenumbers under the name (variable) 'sTeEnSlAg' , and also under 'steenslg'. Then you phone me and ask "Please give me the name you are registered under on my phone, post-fixed with "_of_arrays".
What do you think would be the polite version of my answer?

translating blocks and statements for a DSL

I want to write a simple Ruby DSL to translate some statements and expressions into another language. A basic example would be:
some_function {
t + 2
}
Here, t is not a ruby variable and thus the block can't (and must not!) be evaluated by Ruby. So my best bet would be to use the parsing output (or AST) to do the translation myself. To do so, I can use ParseTree and ruby2ruby. However, I have other constructs that I would like to use, for example:
1.upto(10) {|x|
some_function {
t + x
}
}
Here, I have a local variable and I need to get its value in order to do my translation. However, in the function that does the block evaluation, I don't have access to the local variables of the calling block. If it were a global variable ($x), I could check if its name exists in the global_variables array and in the worst case use eval, but how could I do so for a local variable, if possible at all?
Update:
Just to clear up things. Like I said originally, I'm using ruby2ruby (and hence ParseTree) to get the AST (using to_sexp) corresponding to the block. But when using a local variable inside my block, I encounter the following:
[:dvar, :x]
And thus, I would need to get the value of a variable from its name as a string/symbol. And I can't use method_missing or instance_eval, because I want to translate the whole expression to another language or syntax (like a RPN).
Another solution not based on ParseTree would be welcome nonetheless, since it apparently is not fully supported with Ruby 1.9.
To get the variable values, use the proc's binding:
def some_function(&block)
b = block.binding
p [b.eval("t"), b.eval("x")]
end
t = 1
1.upto(10) {|x|
some_function {
t + x
}
}
Here, t is not a ruby variable and
thus the block can't (and must not!)
be evaluated by Ruby.
Any reason for the "not evaluated" restriction? It seems like method_missing would elegantly handle evaluating the "missing" t variable, but Ruby would automatically dereference the x variable.
You can use instance_eval against an object with t.
class Context
attr_accessor :t
def initialize(_t)
#t = _t
end
end
def some_function(&block)
puts Context.new(1).instance_eval(&block)
end
1.upto(10) {|x|
some_function {
t + x
}
}

Resources