Object Passing & Mutability in Ruby
--
Variable Assignment and Variable References in Ruby:
In ruby our assignment operator =
allows us to point variables at objects.
Let us jump into some code and explore this further.
Below we will assign two variables to a single string object and see how this works.
acknowledgement = "Hello"acknowledgement.object_id #=> 100------------------------------------------salutations = acknowledgementsalutations.object_id #=> 100
Here we have two different variables pointing at (also called referencing) one single object.
First we assign acknowledgement
to the String object “Hello”
.
Then we assign salutations
to point at the String object that acknowledgement
points to.
Variable Reassignment, Mutation, and Non Mutating Methods in Ruby:
acknowledgement = "Goodbye"
acknowledgement.object_id #=> 120 salutations #=> "Hello"
salutations.object_id #=> 100
In the above code we have reassigned acknowledgement
to reference a new String object “Goodbye”
.
Objects in Ruby can either be mutable or immutable. Immutable
means it cannot be modified. Conversely, mutable
means the object can be modified.
For example, numbers and booleans are immutable, while strings are mutable in Ruby. There are no methods available within Ruby that allow you to mutate an immutable object.
1.) int = 102.) p int #=> 10 3.) int = 10 * int4.) p int #=> 100
Wait, doesn’t line 4 show that we just mutated an immutable object?
Most definitely not.
- Here we assign variable
int
a value of10
. - We pass
10
(the object referenced by local variableint
) top
as an argument, this shows us that the assignment from line 1 has been carried out successfully. - Here we reassign
int
to the return value of10 * int
. - We pass the object that
int
points to as argument top
, which outputs100
, this shows us that the reassignment from line 3 has been carried out successfully.
In the above example we have reassigned the variable to point at a different object. We have not mutated the original object.
Assignment simply tells Ruby to bind an object to a variable. It does not mutate the object. When using =
(assignment) remember you are not mutating your object, you are pointing at new objects.
Add AND Assignment Operator +=
This is not a method, this is an assignment operator.
It adds the right operands and left operands and assigns the result to the left.
Take a look at the example below:
a = 10
b = 12a += bp a #=> 22
p b #=> 12
In the above example a += b
is the same as saying…
a = a + b
We are adding the current value of the left side to the right side and saving the result to the left side.
Let’s dig deeper:
What do you think happens with the below code snippet?
def non_mutation(str)
str += '!!'
str.upcase
end
annoyed = "go away"
angry = non_mutation(annoyed)
First, annoyed
is passed to our method non_mutation
. This points the method parameter str
to our string object “go away”
.
str
and annoyed
now point to the same String object “go away”
.
On the second line, we have reassigned str
which now points at the String object “go away!!”
, a completely new object.
At this point annoyed
is still pointing at String object“go away"
. While str
is pointing at the new object “go away!!”
.
String #upcase returns a copy of str with all lowercase letters replaced with their uppercase counterparts.
One the third line, we call the string method #upcase
on the object our variable str
references.
Here, annoyed
is still pointing at the original string object “go away”
.
Now #upcase
is called on str
which returns “GO AWAY!!”
.
As we have seen, the original String object passed in to non_mutation
remains un-mutated.
On the last line, our variable angry
is therefore assigned to the return value of invoking non_mutation
with the string object that annoyed
references, passed as an argument.
The above code snippet is non-mutative.
def mutation(str)
str << '!!'
str.upcase!
end
asking = "come here"
mutation(asking) #=> "COME HERE!!"
On the last line, we invoke mutation
and pass the string object “come here”
that our variable asking
references. At this point, now str
and asking
both reference this object.
The Shovel Operator
String#<<
Appends the given object to str. (This is a mutating method, which modifies the original object it is called on.)
On line 2, the shovel operator <<
mutates the object that both str
and asking
point at, which now is “come here!!”
.
#upcase!
Upcases the contents of str, returning
nil
if no changes were made.
On line 3, we call String method#upcase!
on the string object thatstr
references. #upcase!
is mutative and modifies the object in place, this is returned as it is the last line of the method (again implicit return value).
At this point both str
(the mutation
method’s parameter) and the local variable asking
are pointing at the same object, that is “COME HERE!!”
.
This object has been mutated in place by invoking our methodmutation
and passing the local variable asking
as an argument.
As you can see, this code snippet is mutative.