Scope question

Maybe I’m missing something obvious here, but…
Does this result surprise anyone else or just me?
I was expecting a_hash and an_arr to be local to the method.
Did I just do something stupid?

def scope_q(a_hash,an_arr,a_num)
a_hash[“b”] = 3
an_arr[1] = 5
a_num += 1
end

h = {“a”=>1,“b”=>2,“c”=>3,“d”=>4}
a = [1,2,3,4]
n = 7

scope_q(h,a,n)

p h #> {“a”=>1, “b”=>3, “c”=>3, “d”=>4}
p a #> [1, 5, 3, 4]
p n #> 7

#ruby 1.9.1p243

Harry

On Apr 2, 2010, at 00:39 , Harry K. wrote:

Maybe I’m missing something obvious here, but…
Does this result surprise anyone else or just me?
I was expecting a_hash and an_arr to be local to the method.
Did I just do something stupid?

nope. that’s how it works. Every reference (variable, name, whatever you
want to call it) to an object (with some exceptions) is a pointer to the
actual object. When you call scope_q(h,a,n) you are passing those
references into the method and they’re modified directly.

def scope_q(a_hash,an_arr,a_num)
a_hash[“b”] = 3
an_arr[1] = 5
a_num += 1
end

h = {“a”=>1,“b”=>2,“c”=>3,“d”=>4}
a = [1,2,3,4]
n = 7

scope_q(h,a,n)

p h #> {“a”=>1, “b”=>3, “c”=>3, “d”=>4}
p a #> [1, 5, 3, 4]
p n #> 7

The obvious counter-example is here with a_num/n. Small integers
(fixnums) are optimized so they’re not a pointer to a 32 bit number and
are instead a 30 bit number (2 bits used for flags for special cases).
so 7 gets copied in instead of passing a reference to it. As a result,
the copy gets modified, not the original value at ‘n’.

On Apr 2, 2010, at 12:39 AM, Harry K. wrote:

Maybe I’m missing something obvious here, but…

I am afraid, you are. Hopefully not for long :wink:

Does this result surprise anyone else or just me?
I was expecting a_hash and an_arr to be local to the method.
Did I just do something stupid?

def scope_q(a_hash,an_arr,a_num)

a_hash, an_arr and a_num are local to the method indeed. However, a_hash
and an_array hold references to hash and array object respectively, not
copies. So you are changing the objects passed to this method via those
references. On the other hand, a_num is passed a Fixnum instance which
are implemented as immediate objects and hence passed as copy.

a_hash[“b”] = 3
an_arr[1] = 5
a_num += 1
end

h = {“a”=>1,“b”=>2,“c”=>3,“d”=>4}
a = [1,2,3,4]
n = 7

scope_q(h,a,n)

Here where you pass references to Hash h and Array a, and n as an
immediate object 7.

p h #> {“a”=>1, “b”=>3, “c”=>3, “d”=>4}

Changed in the method

p a #> [1, 5, 3, 4]

Changed in the method

p n #> 7

Not changed as the methods incremented a copy.

#ruby 1.9.1p243

All ruby versions work identically in this area.

On Fri, Apr 2, 2010 at 9:55 AM, Ryan D. [email protected]
wrote:

def scope_q(a_hash,an_arr,a_num)

p h #> {“a”=>1, “b”=>3, “c”=>3, “d”=>4}
p a #> [1, 5, 3, 4]
p n #> 7

The obvious counter-example is here with a_num/n. Small integers (fixnums) are optimized so they’re not a pointer to a 32 bit
number and are instead a 30 bit number (2 bits used for flags for special cases). so 7 gets copied in instead of passing a
reference to it. As a result, the copy gets modified, not the original value at ‘n’.

I would say that in order to explain this is more relevant to say that
Fixnums are inmutable, rather that the fact that they are implemented
as inmediate values. In his method he is not calling a mutating method
on Fixnum, just assigning a new object to the local variable:

def scope_q(a_hash,an_arr,a_num)
a_num += 1 # this is equivalent to a_num = a_num + 1
end

So my point is that, independently on the internal implementation of
Fixnums, the important consideration for his question is the fact that
they are inmutable, and so it’s impossible to call a method that would
modify the object. So it’s not possible to mimic the same examples as
with hashes and arrays.

Jesus.

On Fri, Apr 2, 2010 at 10:25 AM, Harry K. [email protected]
wrote:

a = [1,2,3,4]

scope_q(a)

p a #> [1, 2, 3, 4]

To clarify this: variables are references to objects. When you say this:

an_array[1]= 5

you are calling method “[]=” of the object referenced by an_array.
This object happens to be referenced also in the outer scope of the
method scope_q, in your example by variable a. So, if the method “[]=”
modifies the object, that modification is seen outside when you
reference the object with variable a. On the other hand, when you do
this:

an_arr = [6,6]

you are not calling any method on the object previously referenced by
an_arr. You are assigning a new object to the local variable an_arr.
This does not affect in any way variable “a” in the outer scope, or
the object referenced by a.

Jesus.

a_hash, an_arr and a_num are local to the method indeed. However, a_hash and an_array hold references to hash and array object respectively, not copies. So you are changing the objects passed to this method via those references. On the other hand, a_num is passed a Fixnum instance which are implemented as immediate objects and hence passed as copy.

def scope_q(an_arr)
#an_arr[1] = 5 #This is a reference.
an_arr = [6,6] #This is a local an_arr ?
end

a = [1,2,3,4]

scope_q(a)

p a #> [1, 2, 3, 4]

Harry

Thanks for all the explanations, everybody.
I could pretty much understand what was happening when I saw it happen
but it just surprised me that it worked that way.
I’m sure I have read that method parameters are local variables in that
method.
Either that is not quite right or I have misinterpreted what that means.
Apparently, the latter. I need to read up on this.

Anyway, thanks again.

Harry

2010/4/2 Jesús Gabriel y Galán [email protected]

So my point is that, independently on the internal implementation of
Fixnums, the important consideration for his question is the fact that
they are inmutable, and so it’s impossible to call a method that would
modify the object. So it’s not possible to mimic the same examples as
with hashes and arrays.

Jesus.

You can get the array and hash to mimic the number, though, by calling
non
mutating methods on them.

def scope( old_hash , old_array , old_num )
puts “in scope”
p (old_hash.merge Hash[ ‘e’,5 , ‘f’,6 , ‘g’,7 ]) # => {“a”=>1,
“b”=>2,
“c”=>3, “d”=>4, “e”=>5, “f”=>6, “g”=>7}
p (old_array += [5,6,7]) # => [1, 2, 3, 4, 5,
6,
7]
p (old_num += 1) # => 8
end

hash = {“a”=>1,“b”=>2,“c”=>3,“d”=>4}
array = [1,2,3,4]
num = 7

scope( hash , array , num )

puts
puts “after scope”
p hash # => {“a”=>1, “b”=>2, “c”=>3, “d”=>4}
p array # => [1, 2, 3, 4]
p num # => 7

On Sun, Apr 4, 2010 at 5:26 AM, Harry K. [email protected]
wrote:

Thanks for all the explanations, everybody.
I could pretty much understand what was happening when I saw it happen
but it just surprised me that it worked that way.
I’m sure I have read that method parameters are local variables in that method.
Either that is not quite right or I have misinterpreted what that means.
Apparently, the latter. I need to read up on this.

That is true: method parameters are local variables in that method
that are initially bound to what the caller passes as arguments to the
method.

The rest of your example illustrates what happens when you call
mutating methods on objects, or reassign those variables.

Jesus.

Coming from a C background, I had similar difficulties; worrying about
pointer versus copy parameter semantics. The answer to my concerns
turned out to be… Why should I have to care?

With Ruby, you don’t. It takes a different tact altogether.
This is how I reconciled it.
Try this in IRB:
Set up your outer variables

h = {“a”=>1,“b”=>2,“c”=>3,“d”=>4}
a = [1,2,3,4]
n = 7

But instead of creating your method, just do this:

a_hash, an_arr, a_num = h , a , n # thru parallel assignment

Now, if you execute some of those statements you had in your method,
you should still get the same outcomes, but hopefully not as
surprising in this context.

A quick digression regarding assignment semantics:
The ‘local’ * Variables *( on the left hand side ) are getting their
initial values
By evaluating the * Expresssions * on the right hand side of the
assignment, which evaluate to a list of Objects. i.e. h, a and n
offered up their objects to initialize the variables on the left.

When you invoke a method, Ruby essentailly performs this kind of
parallel assignment:

  • The Arguments in the Invocation are treated as a Right Hand Side
    list of expressions that evaluate to Objects.

  • The Parameters specified in the method Definition act as the
    (local) variables on the Left Hand Side.

To me, that’s one of the great incites in Ruby. It lets us write
incredibly flexible methods, courtesy of the parallel assignment
“splatters”.