Something confusing about ruby parameter-passing - ruby

I've wrote two ruby files for test
test.rb:
#!/usr/bin/ruby
def foo(bar)
bar['key'] = 'value'
end
def my_print(a)
a.each{|k,v|
puts "#{k} => #{v}"
}
end
test_drive.rb:
#!/usr/bin/ruby
require 'test.rb'
hash_test = Hash.new
foo(hash_test)
my_print(hash_test)
It works as what I expect, the output is
key => value
But when I chanege the test.rb to
#!/usr/bin/ruby
def foo(bar)
pre_defined = {'key' => 'value'}
bar = pre_defined
end
def my_print(a)
a.each{|k,v|
puts "#{k} => #{v}"
}
end
Here I used a pre-defined hash, but now it outputs nothing. The "hash_test" is now an empty hash.
Please illustrate me why indeed does this happen?

Here is the simple answer:
In ruby you pass variables with Objects by reference. When you assign an Object to a variable, the variable does not really contain the Object itself within it. Instead, it contains only a reference to that Object.
It may help for you to start seeing the differences between references and objects in order to understand how sending variables as parameters works: the Object itself does not reside in the variable, the variable is pointing to a reference of the Object in memory, because of this, if one variable points to another object and modifies it, that doesn't mean it modified the Object that it was previously referring to.
The important thing for you to understand is that the bar parameter in the foo method is really only a REFERENCE to the Object in memory, not the Object itself. So if bar once pointed to the Object, but it's now referencing another Object, it will modify what it is referencing, not the previous object it pointed to. Here's the code of the last test.rb commented slightly for you to understand it better:
def foo(bar) # here "bar" points to the same object as "hash_test"
pre_defined = {'key' => 'value'} # here a new object is created and referenced
bar = pre_defined # now "bar" points to a new object and forgets about "hash_test"
end
def my_print(a) # here "a" holds a reference to "hash_test" which is empty
a.each{|k,v| # "a" is empty, so it has nothing to put
puts "#{k} => #{v}"
}
end
I hope this helps. In case you need something a little more detailed:
The reason your last version of test.rb prints an empty hash in test_drive.rb is because the Hash Object that the reference "hash_test" points to is not really being modified at all. Instead the foo method, while initially receiving as a parameter called "bar" a reference of the Hash Object that "hash_test" points to, quickly replaces that reference in "bar" with a brand new reference to a brand new Hash Object that the "pre_defined" variable points to. Now "bar" doesn't point to the same Object as "hash_test" does, and so the Object that "hash_test" points to is never being modified. Thus, the Hash Object that "hash_test" contains is never really populated with anything, and is empty when my_print tries to print it.

That is, because Ruby is pass by value, whereby the reference of an object is passed. It’s a similar behavior of what Java does. That means that you have only a copy of the reference within the foo-method and reassigning it does not change the reference outside of this method.
In more detail:
In your first example you pass the Hash to your foo-method. The reference will be copied, so within the foo-method you have a copy of the reference which points to the same object as 'hash_test', but is not the same one. So calling the Hash methods changes the values of the object outside of the method. But in your second example your are not changing values of the given Hash, you assign a new Hash object to the copy of the method reference, which has no effect to the 'hash_test' reference.

Related

Is it possibly to call defined? on a dynamic instance variable name?

Is there a way to call check defined? on a dynamic instance variable name? What I want is to be able to do something like this:
dynamic_attr = 'foo'
define_method :my_method do
if defined? instance_variable_get("##{dynamic_attr}") # Obviously fails, but you see what I'm getting at...
....
end
end
Of course this fails because defined? is a special form that is looking at literals and thus returns "method" in this case because it's looking at the actual instance_variable_get method. And of course instance_variable_get("#foo") is going to return nil if I ever called #foo = nil so it's indistinguishable from having never been set. Is there any way I can see if #foo was defined with the same semantics as defined? but in a dynamic way?
Strange that nobody mentioned that. There is also Object#instance_variable_defined?.
dynamic_attr = 'foo'
define_method :my_method do
if instance_variable_defined?("##{dynamic_attr}")
....
end
end
If I did get your question, you'd need to check whether an instance variable is defined. Object#instance_variable_get retrieves a value of an existing variable or nil if not exists, not the instance variable name.
You can check the definition of dynamically created variable name by its membership to all existing like:
dynamic_attr = 'foo'
define_method :my_method do
if instance_variables.member? "##{dynamic_attr}".to_sym
....
end
end
See array returned with Object#instance_variables contains symbols so you need to convert an argument with String#to_sym to compare apples with apples.

Why can't I change the value of self?

How come I'm allowed to change "self" this way:
self.map! {|x| x*2}
Or this way:
self.replace(self.map {|x| x*2})
But not this way:
self = self.map {|x| x*2}
Why doesn't Ruby allow me to change the object that the "self" variable points to, but does allow me to change the object's attributes?
Not an answer, just a clue.
a=[1,2]
=>[1,2]
a.object_id
=>2938
a.map!{|x| x*2}
=>[2,4]
a.object_id # different data but still the same object
=>2938
a.replace(a.map {|x| x*2})
=>[4,8]
a.object_id # different data but still the same object
=>2938
a = a.map{|x| x*2} # .map will create a new object assign to a
=>[8,16]
a.object_id #different object
=>2940
You can't change your self to another one.
In Ruby, values and variables are references (pointers to objects). Assigning to a variable simply makes it point to a different object; it has no effect on the object the variable used to point to. To change an object, you must call a method (including property getters/setters) on it.
You can think of self as a variable that points to the object the method was called on. If you could assign to it, you could make it point to another object. If you could do that, it would not alter the object the method was called on; instead, you would make it so that any following code in that method that uses self would use that object, not the object the method was called on. This would be super confusing, because self would no longer point to the object the method was called on, which is a basic assumption.

Ruby - passing by value

Is there a way to pass objects by value and not by reference in Ruby? For Example,
class Person
attr_accessor :name
end
def get_name(obj)
obj.name = "Bob"
puts obj.name
end
jack = Person.new
jack.name = "Jack"
puts jack.name
get_name(jack)
puts jack.name
the output should be
Jack
Bob
Jack
instead of
Jack
Bob
Bob
Any help would be appreciated.
No. Ruby passes by reference, not value.
If you need to simulate passing by value, you can use Ruby's Object#clone method. In this case, you'd do something like this:
def get_name(obj)
new_object = obj.clone
new_object.name = "Bob"
puts new_object.name
end
This makes a shallow copy of an object. In other words, an object's instance variables are copied, but the objects the variables reference aren't copied. If you need to do a deep copy, you can read this Stack Overflow post. Ruby doesn't have a one-method way to perform deep copies, but that post describes how to use marshalling and unmarshalling to make a deep copy.
clone and dup work very similarly, but there are some differences. According to the docs:
Object#clone
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference. Copies the frozen and tainted state of obj. See also the discussion under Object#dup.
Object#dup
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference. dup copies the tainted state of obj. See also the discussion under Object#clone. In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance.
This method may have class-specific behavior. If so, that behavior will be documented under the #initialize_copy method of the class.
You can take a look at the dup and clone docs.
Edit
While my answer probably gives what the OP was looking for, it is not strictly correct, with respect to the semantics of passing by reference or value. See the other answers and comments on this page for some more discussion. You can also look at the discussion in the comments here and this post for more information.
Ruby is pass-by-value. Always. Here's a simple program which demonstrates that fact:
def foo(bar)
bar = 'reference'
end
baz = 'value'
foo(baz)
puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
What you are seeing is simply shared mutable state: if an object is mutable, it can be mutated, and if that object is visible through multiple references, then that mutation will be visible through multiple references. Simple as that. It doesn't matter if you call me "Jörg" like my friends do or "son" like my mom does, if I cut my hair, both of you will see that.
If you don't want your objects to be mutated, you need to make them immutable. For example:
class Person
attr_reader :name
private
attr_writer :name
def initialize(name)
self.name = name
end
end
def get_name(obj)
obj.name = "Bob"
puts obj.name
end
jack = Person.new('Jack')
puts jack.name
# Jack
get_name(jack)
# NoMethodError: private method `name=' called for #<Person:0xdeadbeef081542 #name='Jack'>
puts jack.name
# Jack
Now, your Person objects can no longer be changed by other objects. However, note that objects referenced by your Person objects obviously can still be changed:
jack.name << ' the Ripper'
puts jack.name
# Jack the Ripper
jack.name.replace('Bob')
puts jack.name
# Bob
If you want to protect against that, you need to make sure that all the objects referenced by your Person objects are also immutable. For example like this:
class Person
def initialize(name)
self.name = name.freeze
end
end
jack = Person.new('Jack)
jack.name << 'the Ripper'
# RuntimeError: can't modify frozen String
Now, your Person objects are truly immutable. (At least as "truly" as can be in a language with such powerful reflection capabilities as Ruby.)
Unfortunately, we have now done to someone else the very same thing we are trying to protect ourselves against: we are mutating their objects! In Person#initialize, we mutate the String that is passed in by freezeing it. If the method that created the Person wants to do something else with the String, they are in for a nasty surprise:
name = 'Jack'
jack = Person.new(name)
name << ' the Ripper'
# RuntimeError: can't modify frozen String
We can fix that by making a copy of the String first, before freezeing it:
class Person
def initialize(name)
self.name = name.dup.freeze
end
end
name = 'Jack'
jack = Person.new(name)
name << ' the Ripper'
# => 'Jack the Ripper'
Tossing in a formal answer the original question, on top of #Michael's excellent answer:
Is there a way to pass objects by value and not by reference in Ruby?
No. Absolutely not. Not possible. Really no, just forget it.
Or more precisely, if we nitpick on vocabulary, Ruby passes references to objects by value, and the passed objects are mutable as a result.
In Ruby, you must either:
Manually clone/dup inputs and outputs as needed (see Michael's answer); or
Assume that nobody will screw up the internals of your class should you ever decide to not do so.
Rubyists tend to pick option 2, because the internals are built that way (attr_reader, etc. return object state), because of performance reasons, and because creating deep copies of everything you return or manipulate is by no means trivial in practice.
Answering Jörg's comment:
def foo(bar) bar << ' is now mutated' end;
baz = 'Baz';
foo(baz);
puts baz # Baz is now mutated
Adding a further note/example on freezing, since this can give unexpected results as well:
foo = {foo: 'bar'}.freeze # {:foo=>"bar"}
foo[:foo] += 'baz' # RuntimeError: can't modify frozen Hash
foo[:foo] << 'baz' # {:foo=>"barbaz"}

Why do functions called in a ruby rescue block fail to modify the variables?

I have a situation which is comparable with this code:
i=0
def add_one(i)
i+=1
puts "FUNCTION:#{i}"
end
begin
puts "BEGIN:#{i}"
raise unless i>5
rescue
add_one(i)
puts "RESCUE:#{i}"
retry
end
When I run this, I am seeing this output repeatedly:
BEGIN:0
FUNCTION:1
RESCUE:0
This version increments i and completes the program perfectly:
i=0
begin
puts "BEGIN:#{i}"
raise unless i>5
rescue
i+=1
puts "RESCUE:#{i}"
retry
end
Why is there a difference? How can I get a function in the rescue block to actually modify a variable?
This is because in your add_one function you are not manipulating the same i variable as outside of the function.
Let me try to explain that a bit. In Ruby, you deal with generally mutable objects (notable exceptions are numbers, true, false, and nil). A variable is a pointer to such an object. Multiple variables can point to the same object.
a = 123
b = a
Now a and b refer to the very same object. If you assign a new object to one of a or b, they they will refer to different object again, while still retaining the name.
What you have above are local variables. These are only valid inside a scope, mostly the duration of a method. If you create a new local variable (by assigning a value to it), it will only be valid during the duration of the method and will be garbage collected sometime after leaving the method.
Now as I said above, most objects in Ruby are mutable, meaning that you can change them while retaining the variable pointers. An example is adding an element to an array:
array = []
array << :foo
Now the array variable will still refer to the same Array object it got initialized with. But you have change the object. If you would assign the array variable a new array like
array = [:foo]
it would look like the same object, but effectively, they are different (you can verify that be checking the object_id method on every object. If it is the same, you are referring to the very same object)
Now when you do i += 1 in your add_one method, you are effectively running i = i + 1 which will set the i variable to a new value in the local method scope. You are not actually changing the i variable but you assign it a new value in the local method scope. This means that the variable named i on your outer scope (i.e. the begin/end block) will refer to a different object than the i variable in your add_one method. This is because while they have the same name, they have a different scope. The inner scope always masks the outer scope, so while you have variables with the same names in different scopes, they do not interfer in any way (this changes when dealing with instance or class variables)
Unfortunately, as I said above, numbers are immutable. You can't change them. If you assign a new number to a variable, it is a new object. Thus, you can't change the value of a variable pointing to a number in another scope as the code that changes the value.
To circumvent this, you could either return a value from your function and explicitly assign it to your i variable in the outer scope like this
i = 0
def add_one(i)
i+=1
puts "FUNCTION:#{i}"
return i
end
i = add_one(i)
or you could use instance variable of an object like this
class Foo
def initialize
#i = 0
end
def add_one
#i += 1
end
def do_something
begin
puts "BEGIN:#{#i}"
raise unless #i>5
rescue
add_one
puts "RESCUE:#{#i}"
retry
end
end
end
# create a new object and run the instance method
Foo.new.do_something
The "i" in add_one is a local reference, to the parameter--nutshell is that it's a different "i".
You'd need to use a variable in the correct scope.

Ruby newbie question: hashes

i have the following
class test
hash={}
def printHash
puts hash[1]
puts hash[2]
puts hash[3]
end
end
test.new.printHash
this prints:
1
0
1
Why does this happen? how can i test whether or not i have put something in that spot of the hash? or am i missing something
You're well off the mark, but it appears to be doing something because hash is a builtin function which returns a Fixnum hashcode for the object. When you use square brackets on a Fixnum, you get the value of the specific bit. What you want to do is to create an instance variable, which starts with the sigil #. Also, you have to create instance variables within a method, so we'll use the one that's called whenever an object of the class is created, initialize:
class Test
def initialize
#hash = {}
end
def printHash
puts #hash[1]
puts #hash[2]
puts #hash[3]
end
end
Now you'll find this prints nil for all three. To test whether a hash has a value for a specific key, you can use has_key?.
Basically 'hash' is out of scope, what you are referencing in your printHash function is a different object altogether, normally it would be nil (a new unassigned object) but as Pesto points out 'hash' is a built in function - somewhat confusing this explanation.
By putting an '#' sign in front of the your variable and assigning it in the initialize method (which is called after 'new') it becomes available in the entire instance of your object.

Resources