Method arguments

T

Tookelso

Hello,

Sorry for asking this basic question, but I couldn't find it in the
PickAxe book.

When you pass an argument to a method, is it passed by reference, or by
value?
Or, does it depend on the object's type?

For example, in the simple script below, the program outputs "0".
#-----------------------------------
def test(c_count)
c_count = c_count + 1
end
# begin program
c_count = 0
test(c_count)
puts c_count
#-----------------------------------
I realize that for simple things, it's better to return a value from
the method, but I would like to know *why* it's not modifying my
"c_count" value.

I'm using ruby 1.8.2 (2004-11-06) [i386-mswin32]
Thanks in advance,

--Nate
 
M

Martin DeMello

Tookelso said:
When you pass an argument to a method, is it passed by reference, or by
value?

Passed by reference to an object.
For example, in the simple script below, the program outputs "0".
#-----------------------------------
def test(c_count)
c_count = c_count + 1

This creates a new object, gives it the value c_count+1 and has the
c_count variable point to it.

Try the following:

def test1(str)
str = str.upcase
end

def test2(str)
str.upcase!
end

a = "hello"
b = "world"
puts test1(a) #=> HELLO
puts test2(b) #=> WORLD
puts a #=> hello
puts b #=> WORLD

In test1, the variable is pointed to a new object. In test2, the object
to which the variable points is modified via one of its 'destructive'
methods. In general, unless an object provides a mutation method, you
can't change it this way.

Note that the *variable* is local to the method body, and doesn't change
which object the outside variable refers to.

martin
 
M

Mikael Brockman

Tookelso said:
Hello,

Sorry for asking this basic question, but I couldn't find it in the
PickAxe book.

When you pass an argument to a method, is it passed by reference, or by
value?

By value. But remember that the value of a variable is always a
reference! (That's not *technically* true for Fixnums, but since
Fixnums are entirely immutable, the abstraction holds up.)
Or, does it depend on the object's type?

For example, in the simple script below, the program outputs "0".
#-----------------------------------
def test(c_count)
c_count = c_count + 1
end
# begin program
c_count = 0
test(c_count)
puts c_count
#-----------------------------------
I realize that for simple things, it's better to return a value from
the method, but I would like to know *why* it's not modifying my
"c_count" value.

(In this explanation, I'm ignoring the fact that Fixnums are special --
that fact is a pure implementation detail. Just an optimization.)

When you initialize c_count, the 0 is evaluated first. Ruby will
allocate space for a Fixnum. Let's say it allocates some bytes over at
the memory location 0xCAFEBABE. It copies 0x0000, or whatever the
machine representation of 0 is, to 0xCAFEBABE.

Then it creates a new instance variable. Sets its value to 0xCAFEBABE.

When you call ``test'', c_count is passed by value, as always. The
value passed is 0xCAFEBABE.

Now, what happens when you receive an argument is this: a new instance
variable is created. This c_count has nothing in common with the other
c_count -- except that they happen to share values. Of course, you
could name the test parameter ``snuggly_taco'', and the program would
work the same.

When you assign to snuggly_taco, the only thing that happens is that the
value of snuggly_taco is changed, to the address of that new number
you're making. This affects neither 0xCAFEBABE nor c_count.

If you really want to change the value of an instance variable from
another function -- bad luck. You can't.

Actually, I lied. You can do it. If you use Florian Groß's
binding_of_caller and eval.

But that's an extremely obscure hack. Avoid it! The clean solution is,
as always, adding another layer of indirection. Make it so that by
setting something you actually can change -- like an instance variable
-- you change the effective value.

| class Box
| attr_accessor :value
|
| def initialize value=nil
| @value = value
| end
| end
|
| def test c_count
| c_count.value = c_count.value + 1
| end
|
| c_count = Box.new 0
| test c_count
| puts c_count

This box is perfectly analogous with a C pointer (except, of course,
that you can't do pointer arithmetic). Instead of ``*foo'', we write
``foo.value''.

(By the way, is there a class like this in the stdlib?)

mikael
 
G

Glenn Parker

Mikael said:
By value. But remember that the value of a variable is always a
reference!

Of all the explanations so far, I liked this one best. Thanks, Mikael!

If Ruby really passed arguments by reference (like e.g. C++), then
Tookelso would not be confused. In reality, Ruby passes a copy of the
caller's reference. This copy must be handled rather gently, otherwise
the object referenced from caller's scope quickly becomes inaccessible.

It's a subtle point, and it's easy to see why people familiar with
traditional call-by-reference semantics would be misled. I know I
didn't fully appreciate it until now.
 
T

Tookelso

Thanks to all for your explanations. This helped clear up my
confusion.

I will use the return values of the methods to modify values, like in
Perl.

--Nate
 
G

Glenn Parker

Nash said:
Hmmm. Is it safe to pass a Hash and modify it in a function then use it?

for example:

a = Hash.new
modify(a)
do_something_useful(a)

after reading the response below, it's making me think how hashs behave
and whether it's safe to do the above.

Sure, it's safe, and it (probably) does exactly what you would expect.

Ignore the following if you don't want to entertain my rambling mind.

What if Hash was immutable (like Fixnum)?

class Hash
private :[]=

def assign(key, val)
copy = self.clone
copy[key] = val
return copy
end
end

x = { "a" => 1 }
y = x
x = x.assign("a", 3)
# y["a"] is still 1

Not terribly useful or efficient. :)

Note: I think it would be more useful if Ruby's built-in assignment
operator evaluated to the LHS instead of the RHS.

What if we want to go the other way and make Fixnum mutable (like Hash)?
Ruby won't override basic assignment (=), so use =~ instead.

class MutableFixnum
def initialize(v)
@value = v.to_i
end
def =~(v)
@value = v.to_i
end
def +(a)
@value + a.to_i
end
def inspect
@value.to_s
end
end

class Fixnum
def to_mutable
return MutableFixnum.new(self)
end
end

x = 1.to_mutable
y = x
x =~ x + 1
p y => 2

What fun! But maybe not what you would have expected.

Apparently, we prefer it when variables refering to the same Hash all
change when one of those variables is operated on. Yet, it feels
strange when variables refering to the same "number" all change when one
of those variables is operated on.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,167
Messages
2,570,911
Members
47,453
Latest member
MadelinePh

Latest Threads

Top