Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't this work if in Ruby everything is an Object?

Considering that in the Ruby programming language everything is said to be an Object, I safely assumed that passing arguments to methods are done by reference. However this little example below puzzles me:

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil

As you can see the original Object wasn't modified, I wish to know why, and also, how could I accomplish the desired behavior ie. Getting the method to actually change the object referenced by its argument.

like image 932
jlstr Avatar asked Oct 07 '11 14:10

jlstr


People also ask

Why everything in Ruby is an object?

Practically everything in Ruby is an Object, with the exception of control structures. Whether or not under the covers a method, code block or operator is or isn't an Object, they are represented as Objects and can be thought of as such.

Is everything a object?

Summary. In Python, everything is an object. Classes are objects, instances of classes are objects, modules are objects, and functions are objects. Anything that you can point a variable to is an object.

What in Ruby is not an object?

Blocks are not objects in Ruby. We need to use Proc, lambda or literal constructor ->, to convert blocks into objects. In Smalltalk, blocks are objects. The statement: Everything is an object is true for Smalltalk but not for Ruby.

What is the difference between a class and an object Ruby?

An object is a unit of data. A class is what kind of data it is.


1 Answers

The way Ruby works is a combination of pass by value and pass by reference. In fact, Ruby uses pass by value with references.

You can read more in the following threads:

  • Pass by reference or pass by value
  • Pass by reference?

Some notable quotes:

Absolutely right: Ruby uses pass by value - with references.

irb(main):004:0> def foo(x) x = 10 end
=> nil
irb(main):005:0> def bar; x = 20; foo(x); x end
=> nil
irb(main):006:0> bar
=> 20
irb(main):007:0>

There is no standard way (i.e. other than involving eval and metaprogramming magic) to make a variable in a calling scope point to another object. And, btw, this is independent of the object that the variable refers to. Immediate objects in Ruby seamlessly integrate with the rest (different like POD's in Java for example) and from a Ruby language perspective you don't see any difference (other than performance maybe). This is one of the reasons why Ruby is so elegant.

and

When you pass an argument into a method, you are passing a variable that points to a reference. In a way, it's a combination of pass by value and pass by reference. What I mean is, you pass the value of the variable in to the method, however the value of the variable is always a reference to an object.

The difference between:

def my_method( a )
  a.gsub!( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'ruby is awesome'

and:

def your_method( a )
  a = a.gsub( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'foo is awesome'

is that in #my_method, you are calling #gsub! which changes the object (a) in place. Since the 'str' variable (outside the method scope) and the 'a' variable (inside the method scope) both have a "value" that is a reference to the same object, the change to that object is reflected in the 'str' variable after the method is called. In #your_method, you call #gsub which does not modify the original object. Instead it creates a new instance of String that contains the modifications. When you assign that object to the 'a' variable, you are changing the value of 'a' to be a reference to that new String instance. However, the value of 'str' still contains a reference to the original (unmodified) string object.

Whether a method changes the reference or the referenced object depends on the class type and method implementation.

string = "hello"

def changer(str)
  str = "hi"
end

changer(string)
puts string
# => "hello"

string is not changed because the assignment on strings replaces the reference, not the referenced value. I you want to modify the string in place, you need to use String#replace.

string = "hello"

def changer(str)
  str.replace "hi"
end

changer(string)
puts string
# => "hi"

String is a common case where the most part of operations works on clones, not on the self instance. For this reason, several methods have a bang version that executes the same operation in place.

str1 = "hello"
str2 = "hello"

str1.gsub("h", "H")
str2.gsub!("h", "H")

puts str1
# => "hello"
puts str2
# => "Hello"

Finally, to answer your original question, you cannot change a String. You can only assign a new value to it or wrap the string into a different mutable object and replace the internal reference.

$wrapper = Struct.new(:string).new
$wrapper.string = "String"

def changer(w)
  w.string = 1
end

changer($wrapper)

puts $wrapper.string
# => 1
like image 139
Simone Carletti Avatar answered Oct 04 '22 20:10

Simone Carletti