scope question

H

Harry Kakueki

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
 
R

Ryan Davis

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"] =3D 3
an_arr[1] =3D 5
a_num +=3D 1
end
h =3D {"a"=3D>1,"b"=3D>2,"c"=3D>3,"d"=3D>4}
a =3D [1,2,3,4]
n =3D 7
=20
scope_q(h,a,n)
=20
p h #> {"a"=3D>1, "b"=3D>3, "c"=3D>3, "d"=3D>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'.
 
G

Gennady Bystritsky

Maybe I'm missing something obvious here, but...

I am afraid, you are. Hopefully not for long ;-)
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?
=20
=20
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 an=
d an_array hold references to hash and array object respectively, not copie=
s. So you are changing the objects passed to this method via those referenc=
es. On the other hand, a_num is passed a Fixnum instance which are implemen=
ted as immediate objects and hence passed as copy.
a_hash["b"] =3D 3
an_arr[1] =3D 5
a_num +=3D 1
end
=20
h =3D {"a"=3D>1,"b"=3D>2,"c"=3D>3,"d"=3D>4}
a =3D [1,2,3,4]
n =3D 7
=20
scope_q(h,a,n)

Here where you pass references to Hash h and Array a, and n as an immediate=
object 7.
=20
p h #> {"a"=3D>1, "b"=3D>3, "c"=3D>3, "d"=3D>4}

Changed in the method
p a #> [1, 5, 3, 4]

Changed in the method

Not changed as the methods incremented a copy.
=20
=20
#ruby 1.9.1p243

All ruby versions work identically in this area.
 
H

Harry Kakueki

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 cop=
ies. So you are changing the objects passed to this method via those refere=
nces. On the other hand, a_num is passed a Fixnum instance which are implem=
ented as immediate objects and hence passed as copy.
def scope_q(an_arr)
#an_arr[1] =3D 5 #This is a reference.
an_arr =3D [6,6] #This is a local an_arr ?
end

a =3D [1,2,3,4]

scope_q(a)

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



Harry
 
J

Jesús Gabriel y Galán

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 ac=
tual 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)
=A0a_hash["b"] =3D 3
=A0an_arr[1] =3D 5
=A0a_num +=3D 1
end
h =3D {"a"=3D>1,"b"=3D>2,"c"=3D>3,"d"=3D>4}
a =3D [1,2,3,4]
n =3D 7

scope_q(h,a,n)

p h =A0#> {"a"=3D>1, "b"=3D>3, "c"=3D>3, "d"=3D>4}
p a =A0#> [1, 5, 3, 4]
p n =A0#> 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 va=
lue 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 +=3D 1 # this is equivalent to a_num =3D 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.
 
J

Jesús Gabriel y Galán

and an_array hold references to hash and array
object respectively, not copies. So you are changing the objects passed t=
o this method via those references. On the other hand,
a_num is passed a Fixnum instance which are implemented as immediate obje= cts and hence passed as copy.
def scope_q(an_arr)
=A0#an_arr[1] =3D 5 =A0#This is a reference.
=A0an_arr =3D [6,6] #This is a local an_arr ?
end

a =3D [1,2,3,4]

scope_q(a)

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

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

an_array[1]=3D 5

you are calling method "[]=3D" 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 "[]=3D"
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 =3D [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.
 
J

Josh Cheek

2010/4/2 Jes=FAs Gabriel y Gal=E1n said:
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 ]) # =3D> {"a"=3D>1, "b"=
=3D>2,
"c"=3D>3, "d"=3D>4, "e"=3D>5, "f"=3D>6, "g"=3D>7}
p (old_array +=3D [5,6,7]) # =3D> [1, 2, 3, 4, 5=
, 6,
7]
p (old_num +=3D 1) # =3D> 8
end



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

scope( hash , array , num )

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

Harry Kakueki

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
 
J

Jesús Gabriel y Galán

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.
 
R

rlf

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".
 

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

Similar Threads


Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top