Changing value of ruby variables/references - ruby

I just stumbled upon something i don't quite understand. I know that variables in ruby are references. So that awesome stuff is possible. But when i pass a variable to a method, it behaves strangely:
my_var_a = "nothing happend to me"
my_var_b = "nothing happend to me"
def parse_set(my_var_set)
my_var_set = "my value changed"
end
def parse_sub(my_var_sub)
my_var_sub.sub! /(.*)/, "my value changed"
end
parse_set(my_var_a)
parse_sub(my_var_b)
my_var_a # => "nothing happend to me"
my_var_b # => "my value changed"
Can you explain to me why it works with sub! and = leaves the object unchanged? How can I avoid to use sub! but having the same result?

my_var_a and my_var_set are different references, but they point at the same object. If you modify the object in my_var_set, the change shows up in my_var_a. However, if you repoint my_var_set at a new object, that doesn't change what my_var_a points at.
Edit: clarification...
What Ruby does is called passing references by value. When you say
my_var_a = "nothing happend to me"
Ruby saves the string "nothing happend to me" in a memory location (let's call it 1000), and saves the my_var_a reference in another memory location (let's say 2000). When your code uses my_var_a, the interpreter looks at location 2000, see that it points to 1000, then gets the actual string value from 1000.
When you call parse_set(my_var_a), Ruby actually creates a new reference named my_var_set and points it to the string that my_var_a was pointing at (memory location 1000). However, my_var_set is a copy of the my_var_a reference -- let's say my_var_set was created at memory location 3000. my_var_a and my_var_set are 2 completely different references in memory, they just happen to point at the same exact memory location which holds the string value.
The statement my_var_set = "my value changed" in parse_set creates a new string in memory and points my_var_set at that new memory location. However, this doesn't change what the original my_var_a reference points at! Now that my_var_set points at a different memory location, nothing that you do to that variable will affect my_var_a.
The same reference copy happens for parse_sub as well. But the reason that parse_sub changes the string is because you're calling a method directly on the my_var_sub reference. When you do this, the interpreter gets the object that my_var_sub is pointing at and then modifies it. So that change will show up in the my_var_a reference, because it still points at the same string.

irb(main):008:0* a = 'abc'
=> "abc"
irb(main):009:0> a.replace('def')
=> "def"
irb(main):010:0> a
=> "def"
I've had to use replace exactly zero times in all the years I've been programming in Ruby. I wonder if there's a better design for your code.
Most string methods which change the receiver are suffixed by ! (sub!, capitalize!, chomp!, etc.) Some that change the receiver are not suffixed by ! (replace is one). If you call a method that changes the receiver, any and all references to that object will see the change. if you call a method that does not change receiver, the method returns a new string.
gsub does not modify the receiver, but instead returns a new instance of String:
a = "foo"
b = a
p a.sub(/o/, '#') # "f##"
p a # => "foo"
p b # => "foo"
gsub! does modify the receiver:
a = "foo"
b = a
p a.sub!(/o/, '#') # => "f#o"
p a # => "f#o"
p b # => "f#o"

"How can I avoid to use sub! but having the same result?"
def parse_set()
"my value changed"
end
a = parse_set()
Set it as an instance variable of an object (although this works just in your main script, I recommend making your own class if you want to use the instance variables approach).
#my_var_a = "nothing happend to me"
def parse_set()
#my_var_a = "my value changed"
end

Related

Does 'upcase!' not mutate a variable in Ruby?

I'm just trying to make sure I understand what's happening here. I get that += is reassignment so maybe that's why str isn't modified but why doesn't upcase! modify str here either?
def change_me(str)
str += "?"
str.upcase!
end
question = "whats your name"
change_me(question)
puts question
'whats your name'
=> nil
Does 'upcase!' not mutate a variable in Ruby?
It is impossible for a method to mutate a variable. Ruby is an object-oriented language, so methods can mutate objects (more precisely, a method can mutate its receiver), but variables aren't objects in Ruby. (Like most other languages as well, there is almost no language where variables are objects.)
The only way to mutate a variable is through assignment. Note that we generally don't talk about "mutating" variables, we talk about "re-binding" or "re-assigning".
I'm just trying to make sure I understand what's happening here. I get that += is reassignment so maybe that's why str isn't modified but why doesn't upcase! modify str here either?
Again, you are confusing variables and objects. upcase! modifies the object that is referenced by str, but it does not modify str.
It sounds like you expect Ruby to be a pass-by-reference language, but it is not. Ruby is purely pass-by-value, always, no exceptions. More precisely, the value being passed is an unmodifiable, unforgeable pointer to an object.
Here's what happens, following the flow of execution:
question = "whats your name"
The string literal "whats your name" is evaluated, resulting in a String object with the content whats your name.
The local variable question is initialized with an immutable, unforgeable pointer to the string object created in step #1.
change_me(question)
The local variable question is dereferenced, resulting in the immutable, unforgeable pointer to the string object created in step #1.
A copy of that pointer is made.
The copy from step #4 is placed into the argument list of the call to change_me
str += "?"
Inside of the change_me method body, the parameter binding str is bound to the copied immutable unforgeable pointer from step #4 and #5.
This line desugars into str = str + "?", so what happens is:
str is dereferenced, resulting in the copied immutable, unforgeable pointer from step #4, #5, and #6.
We follow the pointer and send the message + to the object with an immutable, unforgeable pointer to the string object created by evaluating the string literal "?" as an argument.
String#+ returns a new string (or, more precisely, an immutable, unforgeable pointer to a new string).
str is re-bound to the new immutable, unforgeable pointer returned by the call to str+("?").
str.upcase!
str is dereferenced, resulting in the new immutable, unforgeable pointer from step #7c #7d.
We follow the pointer and send the message upcase! to the object.
String#upcase! will mutate the receiver object (in this case, the newly created string from step #7c) to make all letters uppercase.
String#upcase! will return either an immutable, unforgeable pointer to the receiver object itself (i.e. the pointer that was used to call the method) if it did any changes to the receiver, or it will return an immutable, unforgeable pointer to the object nil if the string was already uppercase or didn't contain any letters.
Back to change_me(question)
This return value, however, is just ignored, it is thrown away, it is not printed, not assigned to a variable, not passed as an argument to a different method, not stored in a data structure.
puts question
Okay, I will save the details now that the variable is dereferenced, etc.
The crucial part is: the variable question was never touched, it was never re-assigned, so it still contains the exact same thing it contained the whole time: the immutable, unforgeable pointer to the string object from steps #1 and #2.
We assigned this object to the variable, and we:
never re-assigned the variable, so the variable still points to the same object
never asked the object to mutate itself, so the contents of the object are still the same
Therefore, the object is still unchanged, and the variable still points to the same object, and thus we get the result that nothing has changed.
We changed the binding for the str parameter binding inside of the change_me method, but that binding is local to the method. (Parameter bindings are effectively equivalent to local variables.) Therefore, it ceased to exist the moment the method returned.
And we changed the newly created string object, but since we never obtained a pointer to this object, there is no way that we can reach it. One pointer was stored in str, but that is gone. Another pointer was returned from change_me, but we threw that away, so that is gone, too. Since there is no reference this string object, the object is unreachable.
In fact, the change_me method doesn't do anything at all that can be observed from the outside. It creates a string object, then mutates it, but no reference to this object ever leaves the method. Therefore, it is as good as if the mutation never happened, and the string object never existed in the first place.
In fact, a sufficiently clever compiler would be able to optimize your entire code to this:
puts "whats your name"
when you are doing str += "?" you are creating a new string, so str points to a different string than the one you are passing as an argument.
What you are doing is essentially this:
def change_me(str)
new_str = str + "?"
new_str.upcase!
end
That is why your previous string is not being changed. If you want the function to have side effects, you should do:
def change_me(str)
str << "?"
str.upcase!
end
However, I think modifying strings in place is a bit questionable. I think it would be safer to return a new string and overwrite your reference if needed.
Let's see if I can boil all this down a bit for you. First, have a careful look at Mario's "what you are doing is essentially this" code example. Understand that you are calling your #upcase! method on an entirely new object, since you reassigned str to a new object when you tried to tack a ? onto it.
Now, have a look at this:
def change_me(str)
str.upcase!
42
end
x = 'hello'
puts x # => hello
change_me(x)
puts x # => HELLO
As you can see, this code returns 42. Now, as Douglas Adams has told us, 42 is the meaning of life. But if so, the meaning of life is entirely irrelevant here, because as Jörg has been trying to explain to you, you don't do anything with the return value of your method call.
You will also see that your str object does get mutated here. That's because in this case, you haven't reassigned the str variable to a different object inside your method, as your code does. (Again, look carefully at Mario's first example.)
Now, if, in your method, you want to tack something onto the end of the object that you send into your method, you need to use << instead of +. Look at Mario's second code example, and give that a try.
To dig down into this and learn it thoroughly, the #object_id method is very useful. Try running this code:
def change_me(str)
p str.object_id
str += "?"
p str.object_id
str.upcase!
p str.object_id
end
def change_me_2(str)
p str.object_id
str << "?"
p str.object_id
str.upcase!
p str.object_id
end
If you spend some time evaluating object ids, you'll sort this out for yourself pretty quickly.
Finally, I second Mario's point of view that modifying strings in place is a bit questionable in practice. Unless there's some reason that you can't do it this way, I would do this:
def change_me(str)
str.upcase + '?'
end
And then:
question = "what's your name"
question = change_me(question)
Or simply:
question = change_me("what's your name")
Finally, here's a little quiz. Take your code and change the way you call it so:
def change_me(str)
str += "?"
str.upcase!
end
question = "whats your name"
puts change_me(question)
Why does this do what you intended? Now, change str.upcase! to str.upcase, and you will see that it also does what you intended. Why doesn't it make any difference whether you use the ! or not?

+= operator appears to modify frozen string

I am using ruby freeze method. As far as the definition of freeze is considered, it freezes the value of the object on which it is called. We can not modify the value of that object after it. I have to achieve same task, I have a an object and I am executing following code
a = "Test"
a.freeze
a += "this string"
puts a
This gives outputs as follows:
Test this string
[Finished in 0.0s]
Why it is modifying my frozen string?
Nothing is modifying your frozen String
You are re-assigning a to a new String with
a += "this string"
which is internally the same in Ruby as
a = a + "this string"
When you add two String objects in Ruby, it will create a new String containing the result (this is normal behaviour for + operator on most objects that support it). That leaves the original "Test" and "this string" values unchanged. The original, frozen String (containing "Test") will remain in memory until it is garbage collected. It can be collected because you have lost all references to it.
If you attempted to modify the object in place like this:
a << "this string"
then you should see an error message RuntimeError: can't modify frozen String
Basically, you have confused a, the local variable, with the String object to which it is pointing. Local variables can be re-assigned at any time, independently of the objects stored by Ruby. You can verify this is what has happened in your case by inspecting a.object_id before and after your a +=... line.
The freeze method prevents you from changing an object, it turns an object into a constant.
s1 = "its testing"
s1.freeze
puts "Object ID ===", s1.obejct_id
So, after freezing an object, an attempt to modify it results in TypeError.
s1 << "testing again"
it will give, RuntimeError: can't modify frozen String
BUT,
freeze operates on an object reference, not on a variable
s1 += "New Testing"
puts "Object ID ===", s1.obejct_id
will point to evaluated to a new object and also check its object ID.
For detailed information refer this site,
http://rubylearning.com/satishtalim/mutable_and_immutable_objects.html

Multiple method calling

This just returns the original name.
name = "George"
name.reverse.upcase!
puts(name)
I'm wondering why and if there is any way to do what I tried above.
reverse returns a new string. upcase! upcases the string it is called on in-place. You are creating a new reversed string, upcasing that new string, and then never using it again.
If you wanted to reverse and upcase the original string you could name.reverse!.upcase!
The methods you are calling do not affect the calling object. name.reverse will return a new String and leave the original alone. What you want to do is reassign name after your call.
name = George
name = name.reverse.upcase
There is a gotcha here in that bang methods, ending in ! will often modify the object being operated upon. So you could do something like below:
name = George
name.reverse!.upcase!
In general, I would avoid the ! methods unless you have a good reason. The first example of setting "name = " is very clear, easy to read and unambiguous.
Perhaps this will help explain what's happening:
name = "George" # => "George"
name.object_id # => 70228500576040
object_id is the memory reference for the actual variable, in other words, where it lives as the script is running.
reversed_name = name.reverse # => "egroeG"
reversed_name.object_id # => 70228500574980
We can tell that reverse created a new variable because the object_id is different from that of name.
upcased_reversed_name = reversed_name.upcase! # => "EGROEG"
upcased_reversed_name.object_id # => 70228500574980
The upcase! method modified the same variable as reversed_name.
If we use upcase instead, the object_id changes because a new version of the variable is created:
upcased_reversed_name = reversed_name.upcase # => "EGROEG"
upcased_reversed_name.object_id # => 70228500572960
upcased_reversed_name # => "EGROEG"
The short lesson is you can't assign a ! method result to a variable because it acts on the original variable and changes it in place.

Ruby variables referencing other variables

I have two variables, one (b) that references the other (a). When I modify a with a method, b is also modified:
a = "TEXT"
b = a
print b
#=> TEXT
a.downcase!
print b
#=> text
However, when I modify a directly, b retains its value:
a = "TEXT"
b = a
print b
#=> TEXT
a = "Something Else"
print b
#=> TEXT
Why is the behavior of b different when the variable it initially referenced is modified directly as opposed to by a method?
Is this an improper thing to do in Ruby, and if so, what would a better practice be for referencing one variable with another?
Ruby works with references, and you are making a little mistake in there.
This:
a.downcase!
as the 'bang' method suggests, is changing the value referenced by a.
So a is still the referencing the same object, which was just changed by the downcase! method
But this:
a = "Something Else"
is actually saying to a to reference a new object which happens to also be a string.
Since b was referencing another object and that object didn't changed, it still prints TEXT.
You can use the object_id to see what is going on here.
a = "text"
a.object_id
=> 70200807828580
b = a
b.object_id
=> 70200807828580 # b points to the same object that a does.
a = "new"
a.object_id
=> 70200807766420 # a now points to a new object
b.object_id
=> 70200807828580 # b still points to the original object.
So you see that the variable actually doesn't store the object itself. Instead it stores the id of the object. That's why if you copy an object you usually just copy the id of it rather than creating a whole new object.

Ruby hash value converts to string, don't know why

I have a 'strange' problem, the following code converts the location lat value into a string (With a + sign for each iteration) leading to an eventual exception when comparing values. I've tried the code with values for another location and it works fine. The only difference is that the other numbers were negatives.
location= {:lng => 2.0781,:lat => 41.2899}
while location[:lat] < top
sleep(1)
checkTweets(location)
bottom+=0.075
location[:lat] = bottom
end
The issue occurs before entering the check tweets location. The values for the hash are as follows
To to conclude, my question is can anyone explain to me why location[:lat] ends up being a string in this circumstance?
Bottom is initialized as 30.0400 which is assigned to the :lat value. The checkTweets method simply writes a file based on a mongodb query.
Right I found the solution to this. It was the twitter library which was turning the Hash float values into strings.
Am I wrong in assuming that the scope of the variable in the checkTweets method should not impact the location variable here, they are both declared in seperate methods, they are not class level.
I wrong in assuming that the scope of the variable in the checkTweets method should not impact the location variable here, they are both declared in seperate methods, they are not class level.
No, but variable scope is not the issue here. The location variable is local to your method and as such cannot be changed by the checkTweets method. That is correct.
However the object that is referenced by the location variable can be changed from the checkTweets method and that is exactly what happens (though I have to say that mutating arguments is very bad style).
A little example to illustrate reference semantics and mutation in ruby:
def f1(arr)
arr = [1,2,3] # Changes the variable arr, which is local to f1
# This change is not visible on the outside
end
def f2(arr)
arr.concat [1,2,3] # Changes the object that arr refers to
# This change will be visible any place where the same
# array is referenced
end
foo = [42,23]
f1(foo)
# foo is still [42, 23]
f2(foo)
# foo is now [42, 23, 1, 2, 3]
Here the variable foo hasn't been changed to refer to another object (that would not be possible from inside a method), but the object that foo refers to has been changed. The same happens in your checkTweets method.

Resources