why one array continues to grow after repeated call

L

Li Chen

Hi all,

I write a script for studying purpose:
create a new array(b1) based on an existing one (a1).

I expect that once an array is set up it should be fixed regardless how
many times it is called. But I find array b1 continues to grow if I call
it repeatedly? I can't find an explantion for that. Any idea?

Thank you very much in advance,

Li



C:\Users\Alex>irb
irb(main):001:0> class A
irb(main):002:1> def initialize
irb(main):003:2> @a1=[]
irb(main):004:2> @b1=[]
irb(main):005:2> method1
irb(main):006:2> end
irb(main):007:1>
irb(main):008:1* def method2
irb(main):009:2> (method1.size).times{@b1<<10}
irb(main):010:2> return @b1
irb(main):011:2> end
irb(main):012:1>
irb(main):013:1*
irb(main):014:1* def method1
irb(main):015:2> @a1=[1,2,3,4]
irb(main):016:2> return @a1
irb(main):017:2> end
irb(main):018:1>
irb(main):019:1* end
=> nil
irb(main):020:0> ob=A.new
=> #<A:0x2f9f7c @b1=[], @a1=[1, 2, 3, 4]>
irb(main):021:0> ob.method2
=> [10, 10, 10, 10]
irb(main):022:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10]
irb(main):023:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
irb(main):024:0> ob.method1
=> [1, 2, 3, 4]
irb(main):025:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
irb(main):026:0> exit
 
B

Brian Candler

Li said:
I expect that once an array is set up it should be fixed regardless how
many times it is called.

I'm not sure why you expect that.

In your program, you first assign @b1 to an empty array.

In method2, you loop around 4 times pushing '10' onto the end of the
array. Therefore @b1 becomes, each time around the loop,
[10]
[10,10]
[10,10,10]
[10,10,10,10]

If you call method2 again, then again 4 times you push 10 onto the end
of the array, so after that you get
[10,10,10,10,10,10,10,10]

Why would you expect the first four pushes to work, but the second 4
pushes not to work?
 
R

Rick DeNatale

[Note: parts of this message were removed to make it a legal post.]

Hi all,

I write a script for studying purpose:
create a new array(b1) based on an existing one (a1).

I expect that once an array is set up it should be fixed regardless how
many times it is called. But I find array b1 continues to grow if I call
it repeatedly? I can't find an explantion for that. Any idea?

Thank you very much in advance,

You seem to have a common misperception that assigning a value to a ruby
variable acts like a declaration, rather than just a, potentially temporary,
binding of the variable to a particular object.
C:\Users\Alex>irb
irb(main):001:0> class A
irb(main):002:1> def initialize
irb(main):003:2> @a1=[]
irb(main):004:2> @b1=[]


If instance variables were constrained by the declaration, then @a1 and @b1
would have to always refer to empty arrays.
irb(main):005:2> method1

And method1 could have no effect here.
irb(main):006:2> end
irb(main):007:1>
irb(main):008:1* def method2
irb(main):009:2> (method1.size).times{@b1<<10}


What really happens here is that the array referenced by @b1 has @a1.size
10's added each time the method is called.
irb(main):010:2> return @b1
irb(main):011:2> end
irb(main):012:1>
irb(main):013:1*
irb(main):014:1* def method1
irb(main):015:2> @a1=[1,2,3,4]

And this line replaces the empty array bound to @a1 in the initialize method
with a new array instance. The original empty array is now subject to
garbage collection.
irb(main):016:2> return @a1
irb(main):017:2> end
irb(main):018:1>
irb(main):019:1* end
=> nil
irb(main):020:0> ob=A.new
=> #<A:0x2f9f7c @b1=[], @a1=[1, 2, 3, 4]>
irb(main):021:0> ob.method2
=> [10, 10, 10, 10]
irb(main):022:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10]
irb(main):023:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
irb(main):024:0> ob.method1
=> [1, 2, 3, 4]
irb(main):025:0> ob.method2
=> [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
irb(main):026:0> exit


Perhaps this two year old article in my blog might give some insight

http://talklikeaduck.denhaven2.com/articles/2006/09/13/on-variables-values-and-objects
 
L

Li Chen

Brian said:
Li said:
I expect that once an array is set up it should be fixed regardless how
many times it is called.

I'm not sure why you expect that.

In your program, you first assign @b1 to an empty array.

In method2, you loop around 4 times pushing '10' onto the end of the
array. Therefore @b1 becomes, each time around the loop,
[10]
[10,10]
[10,10,10]
[10,10,10,10]

How come this loop happan? I don't want a loop here.
 
R

Rick DeNatale

[Note: parts of this message were removed to make it a legal post.]

Brian said:
Li said:
I expect that once an array is set up it should be fixed regardless how
many times it is called.

I'm not sure why you expect that.

In your program, you first assign @b1 to an empty array.

In method2, you loop around 4 times pushing '10' onto the end of the
array. Therefore @b1 becomes, each time around the loop,
[10]
[10,10]
[10,10,10]
[10,10,10,10]

How come this loop happan? I don't want a loop here.

Because that's what you coded:

irb(main):008:1* def method2
irb(main):009:2> (method1.size).times{@b1<<10}
irb(main):010:2> return @b1
irb(main):011:2> end

$ qri Integer#times
---------------------------------------------------------- Integer#times
int.times {|i| block } => int
------------------------------------------------------------------------
Iterates block int times, passing in values from zero to int - 1.

It's not really clear what you are trying to do with your two methods, why
not just initialize both instance variable in the intialize method?

class A
def initialize
@a1 = [1,2,3,4]
@b1 = [10][email protected] # or more clearly just [10, 10, 10, 10]
end

def method1
@a1
end

def method2
@b1
end
end

I'll leave discussion about using intention revealing method and variable
names for another time.
 
R

Randy Kramer

Perhaps this two year old article in my blog might give some insight
http://talklikeaduck.denhaven2.com/articles/2006/09/13/on-variables-values-and-objects

I'm not the OP but I took a quick glance at the article. Sometime when
I have more time to study it, it may help me a lot, but I had some
trouble with it, and comments about it. I would have left them on that
page but it appears comments are disabled.

The area where I had my first problem was around here:

<quote>
Mutability, and Aliasing

Here's one of those stumbling blocks for those who expect variables in a
uniformly object-oriented language to work like they do in a language
like C or Fortran:

1: a = [1, 2, 3]
2: b = [1, 2, 3]
3: c = a

4: a[1] = 0

5: p a #=> [1, 0, 3]
6: p b #=> [1, 2, 3]
7: p c #=> [1, 0, 3]

...

Line 4 might look like an assignment to the variable a, but it's really
a method call to the array which a happens to be referencing at the
time. And that method (called []=) changes, or mutates that array. That
change will be visible through the variables a, c and any others that
reference that particular array. Multiple references to the same object
are called aliases to that object. They might be named variables, or
referenced which are inside another object:
</quote>

The statement "Line 4 might look like an assignment to the variable a,
but it's really a method call to the array which a happens to be
referencing at the time." is almost like a red herring for me--it's
really not (imho) the thing that is important at that moment in your
example.

The really important thing is the previous statement, and how it works
(and asignment in general, whether you call it a method or whatever):

3: c = a

To me, the thing that is interesting (and finally sinking in) is how
assignments work. (I'm not sure whether I've read this before
somewhere or not.)

What I think will help me is recognizing (I think this is true) that
assignments work in different ways, as follows:

If you assign an object to a variable (like a = [1, 2, 3]), the
assignment works as expected (or at least as most of us expect, I
think ;-)

(I know that's not completely true at the level of Ruby internals--a
isn't the memory containing the array [1, 2, 3] but instead in some (as
you say on the page) pedagological (sp?) sense, points to the memory
area containing the array [1, 2, 3].)

In contrast, when you (attempt to) assign a variable to a variable, Ruby
does something that is a little surprising (at least to me, and perhaps
to others coming from, for example C). Instead of assigning a (the 2nd
variable) to c (the 1st variable), Ruby seaches out the object to which
the variable "points" (the object which the variable references--the
array [1, 2, 3]), and assigns that object to c.

I'm not a C programmer (have tried to learn at times), but it is almost
like the Ruby assignment a = c is handled more like (hmm, I'm trying to
think of the right C syntax, would it be):

c = *a (or would that be c = &a--darn)

(So, at first glance, c is automatically something like a pointer. I
guess in Ruby terms, that's what is called an alias.)

Furthermore, I suspect that you can't just do anything you want (like
you might in C) with that new pointer to a--what do I mean? Well, c
doesn't have any notion of a anymore--c is a pointer (reference) to
what a contained, but it is "direct", it doesn't go through a in any
sense after the assignment. (What could I do with it in c--can't think
of anything atm.)

Ok, I guess that's why it's called an alias--it's another name for that
object.

Anyway, I think this will help me, unless it collides with reality at
some point.

Am I totally off base?

Randy Kramer
 
B

Brian Candler

The statement "Line 4 might look like an assignment to the variable a,
but it's really a method call to the array which a happens to be
referencing at the time." is almost like a red herring for me--it's
really not (imho) the thing that is important at that moment in your
example.

It's crucial. x[y] = z "looks" very much like some sort of an
assignment. BUT IT ISN'T AT ALL.

It is exactly the same as: x.[]=(y,z)

or:

x.send:)[]=, y, z)

(you get into smiley programming if you do that :) It's a method call
to object x; the method has the ungainly name "[]="; and the arguments
are y,z
The really important thing is the previous statement, and how it works
(and asignment in general, whether you call it a method or whatever):

3: c = a

To me, the thing that is interesting (and finally sinking in) is how
assignments work. (I'm not sure whether I've read this before
somewhere or not.)

Yes, that's very important too. You are assigning to a local variable,
which is just a slot on the stack. A local variable is in fact one of
the few things which is *not* an object in Ruby. It *holds* a reference
to an object, but is not an object itself.
What I think will help me is recognizing (I think this is true) that
assignments work in different ways, as follows:

If you assign an object to a variable (like a = [1, 2, 3]), the
assignment works as expected (or at least as most of us expect, I
think ;-)

(I know that's not completely true at the level of Ruby internals--a
isn't the memory containing the array [1, 2, 3] but instead in some (as
you say on the page) pedagological (sp?) sense, points to the memory
area containing the array [1, 2, 3].)

In a quite accurate sense, 'a' is a pointer to the memory containing the
array [1, 2, 3]
In contrast, when you (attempt to) assign a variable to a variable, Ruby
does something that is a little surprising (at least to me, and perhaps
to others coming from, for example C). Instead of assigning a (the 2nd
variable) to c (the 1st variable), Ruby seaches out the object to which
the variable "points" (the object which the variable references--the
array [1, 2, 3]), and assigns that object to c.

No. It simply copies a to c. a contains a pointer to the array, and so
now c also contains a pointer to the same array. It's really that
simple.
I'm not a C programmer (have tried to learn at times), but it is almost
like the Ruby assignment a = c is handled more like (hmm, I'm trying to
think of the right C syntax, would it be):

c = *a (or would that be c = &a--darn)

No, it's "c = a" in C as well.

char *a;
char *c;

a = malloc(123); /* a points to some memory */
c = a; /* c points to the same memory */
(So, at first glance, c is automatically something like a pointer. I
guess in Ruby terms, that's what is called an alias.)

They're called "references", to distinguish them from pointers because
you can't actually use them as pointers (e.g. you can't "increment" the
pointer to point to the next area of memory, as you can in C).

Aliases are just multiple references/pointers to the same thing. But an
object has no idea how many of these may or may not exist.
Furthermore, I suspect that you can't just do anything you want (like
you might in C) with that new pointer to a--what do I mean? Well, c
doesn't have any notion of a anymore--c is a pointer (reference) to
what a contained, but it is "direct", it doesn't go through a in any
sense after the assignment. (What could I do with it in c--can't think
of anything atm.)

You can do exactly the same with a and c. They are exactly equal in
contents and power.

a.first
c.first

both invoke the method "first" on the same object.

HTH,

Brian.
 
B

Brian Candler

Oh I forgot to add - while playing about with this in irb, the method
"object_id" is really useful. It returns the actual pointer. (Well, it's
not *exactly* the pointer; it's a pointer which has been cleverly
encoded to include some information about the type of the object, to
optimise some common operations, but internally Ruby is able to map the
object_id to the actual memory location with some simple bit-masking)

irb(main):001:0> a = "hello"
=> "hello"
irb(main):002:0> b = a
=> "hello"
irb(main):003:0> a.object_id
=> -605500598
irb(main):004:0> b.object_id
=> -605500598
irb(main):005:0> a << " world"
=> "hello world"
irb(main):006:0> a
=> "hello world"
irb(main):007:0> b
=> "hello world"

Here, you can see that a and b really are pointers to the same object.
You could have done `b << " world"` instead of `a << " world"` and got
exactly the same effect. In both cases you are invoking method "<<" with
argument " world" on the object at "memory location"(ish) -605500598.

Contrast with:

irb(main):008:0> a = "hello"
=> "hello"
irb(main):009:0> b = "hello"
=> "hello"
irb(main):010:0> a.object_id
=> -605560548
irb(main):011:0> b.object_id
=> -605574918
irb(main):012:0> a << " world"
=> "hello world"
irb(main):013:0> a
=> "hello world"
irb(main):014:0> b
=> "hello"

You can see clearly that a and b point to different strings, which
happen to start out with the same content.

Incidentally, if a points to a string and you want b to point to a
different string with the same content, you can do

b = a.dup

or

b = String.new(a)

This creates a new String object, and copies the content byte by byte
from the old one into the new one, leaving b pointing at the new one. In
practice this doesn't need to be done very much.

Finally, note that if you write

a += " world"

this is short for

a = a + " world"

Now the expression on the RHS creates a new string object, being the
concatenation of the original string and " world", and then this pointer
is stored in a; a no longer points to what it did before. But assigning
to a doesn't affect any other local variable which points to the
original string. So:

irb(main):018:0> a = "hello"
=> "hello"
irb(main):019:0> b = a
=> "hello"
irb(main):020:0> a.object_id
=> -605642908
irb(main):021:0> b.object_id
=> -605642908
irb(main):022:0> a += " world"
=> "hello world"
irb(main):023:0> a.object_id
=> -605661638
irb(main):024:0> b.object_id
=> -605642908
irb(main):025:0> a
=> "hello world"
irb(main):026:0> b
=> "hello"

You can see that a is pointing to a new object, whereas b is still
pointing to the original one.

I hope this makes things clearer rather than muddier :)

Regards,

Brian.
 
R

Randy Kramer

[Note: parts of this message were removed to make it a legal post.]

It's crucial. x[y] = z "looks" very much like some sort of an
assignment. BUT IT ISN'T AT ALL.

It is exactly the same as: x.[]=(y,z)

or:

x.send:)[]=, y, z)

(you get into smiley programming if you do that :) It's a method call
to object x; the method has the ungainly name "[]="; and the arguments
are y,z

My comment on assignment was sort of a digression (not my main point), but, nevertheless, I can do the following while using object_id (as suggested in your followup) to confirm that I'm still dealing with the same object. (a was leftover from some previous "exercise"):

irb(main):098:0> a
=> [nil, nil, 5, nil, nil, nil, nil, nil, nil, "testy", "foo"]
irb(main):099:0> a.object_id
=> -605519610
irb(main):100:0> a[2] = 3
=> 3
irb(main):101:0> a
=> [nil, nil, 3, nil, nil, nil, nil, nil, nil, "testy", "foo"]
irb(main):102:0> a.object_id
=> -605519610
irb(main):103:0> a.[]=(2,4)
=> 4
irb(main):104:0> a
=> [nil, nil, 4, nil, nil, nil, nil, nil, nil, "testy", "foo"]
irb(main):105:0> a.object_id
=> -605519610
irb(main):106:0> a.send:)[]=, 2, 5)
=> 5
irb(main):107:0> a
=> [nil, nil, 5, nil, nil, nil, nil, nil, nil, "testy", "foo"]
irb(main):108:0> a.object_id
=> -605519610

So, what is the net effect? With any of the three syntaxes (a[2] = 3, a.[]=(2,4), a.send:)[]=, 2, 5)), I change (assign a new value to) the 3rd element of the array a.

What is the practical point of saying it is not an assignment? Yes, I know that the underlying thing is a method, and somehow a[2] = 3 is syntactic sugar (I guess), for a.[]=(2,3), but in the end, it accomplishes an assignment.

I'm not intentionally trying to be obstinate, it just seems there is no practical impact to what happens, or to my thinking process as a programmer, to not consider a[2] = 3 an assignment.
Yes, that's very important too. You are assigning to a local variable,
which is just a slot on the stack. A local variable is in fact one of
the few things which is *not* an object in Ruby. It *holds* a reference
to an object, but is not an object itself.

I'm assuming the same is true for any variable in Ruby, not just local variables?
No, it's "c = a" in C as well.

char *a;
char *c;

a = malloc(123); /* a points to some memory */
c = a; /* c points to the same memory */

Ok, is there still some magic here?. Compare (thinking in C):

Given:

char *a;
char *c;

Would both of these assignments (in C) work as they would in Ruby:

a = [1, 2, 3]
c = a

... or would the first one have to be something like:

a = &([1, 2, 3])
They're called "references", to distinguish them from pointers because
you can't actually use them as pointers (e.g. you can't "increment" the
pointer to point to the next area of memory, as you can in C).

Ahh, that's helpful, I've often wondered what made a pointer a reference instead.
Aliases are just multiple references/pointers to the same thing. But an
object has no idea how many of these may or may not exist.

Ok. And it's the same in C for pointers, iiuc.
Yes, thanks!

Randy Kramer
 
S

Sebastian Hungerecker

Randy said:
What is the practical point of saying it is not an assignment? =A0Yes, I = know
that the underlying thing is a method, and somehow a[2] =3D 3 is syntactic
sugar (I guess), for a.[]=3D(2,3), but in the end, it accomplishes an
assignment.

It accomplishes whatever it has been defined to accomplish. If you do []=3D=
=20
there is no assigment happening unless there's an actual, real assigmnent
in []=3D's method definition. It's not different from any other method in t=
hat=20
regard. Consider this:
class Foo
def []=3D(x,y)
# Do nothing at all
end
end
=46oo.new[1]=3D2 # No assigment whatsoever has taken place.

HTH,
Sebastian
=2D-=20
Jabber: (e-mail address removed)
ICQ: 205544826
 
B

Brian Candler

Randy said:
So, what is the net effect? With any of the three syntaxes (a[2] = 3,
a.[]=(2,4), a.send:)[]=, 2, 5)), I change (assign a new value to) the
3rd element of the array a.

What is the practical point of saying it is not an assignment?

It just so happens that the semantics of the []= method on an Array
object are to replace the x'th element with a pointer to y. However
there is no requirement for any object to behave in such a way.

class Foo
def []=(x,y)
puts "Hello #{x}, today is #{y}"
end
end
a = Foo.new
a["Randy"] = "Friday"

It's just a method call. Objects can, in response to method calls,
mutate (normally that means change what their instance variables point
to; in the case of Array, which is a special built-in class, it means
change the underlying hidden data structure)

But objects mutating is completely different to assignment.

var = expr

means calculate the value of 'expr' (which *always* results in a
reference to some object), and store this reference in the local
variable 'var' (which is a slot on the stack)

This is something I find great about Ruby: everything is a reference. In
C you have to decide whether you are passing a value or a pointer; in
C++ you have to decide whether you are passing a value, a pointer or a
reference :-(
Yes, I
know that the underlying thing is a method, and somehow a[2] = 3 is
syntactic sugar (I guess), for a.[]=(2,3), but in the end, it
accomplishes an assignment.

In the case of an Array object, it accomplishes a modification to that
object's internal state.
I'm assuming the same is true for any variable in Ruby, not just local
variables?

Sure: global variables, instance variables and class variables all live
in different places, but they are just holders of references and are not
objects in themselves.
a = malloc(123); /* a points to some memory */
c = a; /* c points to the same memory */

Ok, is there still some magic here?. Compare (thinking in C):

Given:

char *a;
char *c;

Would both of these assignments (in C) work as they would in Ruby:

a = [1, 2, 3]
c = a

You can't do exactly that in C. One possibility is

char a[3] = { 1, 2, 3 };
char *c;

c = a;

Here a and c are not exactly the same. Arrays in C are just pointers to
memory, and you could use 'a' and 'c' in any context that a 'char *'
pointer would be expected, but whilst you can modify 'c' to point
somewhere else, you cannot modify 'a' (e.g. a++ is illegal, as is a = b)

A bit closer to Ruby is:

char *a;
char *c;

a = malloc(3);
memcpy(a, "\x01\x02\x03", 3);

c = a;

Note that in Ruby, the expression [1, 2, 3] creates a new object every
time it executes. Try for example,

10.times { puts [1, 2, 3].object_id }

The same even applies for Strings:

10.times { puts "Hello".object_id }

There are only a few cases where you get the same object each time round
(e.g. Fixnums, some Regexp literals, symbols, nil, true, false)

B.
 
R

Randy Kramer

Randy said:
So, what is the net effect? With any of the three syntaxes (a[2] = 3,
a.[]=(2,4), a.send:)[]=, 2, 5)), I change (assign a new value to) the
3rd element of the array a.

What is the practical point of saying it is not an assignment?

It just so happens that the semantics of the []= method on an Array
object are to replace the x'th element with a pointer to y. However
there is no requirement for any object to behave in such a way.

class Foo
def []=(x,y)
puts "Hello #{x}, today is #{y}"
end
end
a = Foo.new
a["Randy"] = "Friday"

It's just a method call. Objects can, in response to method calls,
mutate (normally that means change what their instance variables point
to; in the case of Array, which is a special built-in class, it means
change the underlying hidden data structure)

Hmm, don't know how to express this properly (and maybe it's way off
base), but are you hinting at the idea that if I were to try []= on an
immutable data type, then the behavior would not be an assignment. (I'm
not sure what it would do--report an error, create a new object with a
different object_id? (Some quick experiments with a constant seem to
indicate that is the case (actually, it does both).)

That does seem to be what happens at least for this case:

irb(main):224:0> Const = 3.14
(irb):224: warning: already initialized constant Const
=> 3.14
irb(main):225:0> Const.object_id
=> -606494436
irb(main):226:0> Const = 3.00
(irb):226: warning: already initialized constant Const
=> 3.0
irb(main):227:0> Const.object_id
=> -606517176

So now would I say that this is not an assignment? Hmm, I was almost
ready to say yes, but not really--yes, I know it created a new object
instead of changing the value of the old, but the net effect to any
program I've written is that where Const used to be 3.14, it is now
3.00 (I should call this the Indiana legislature assignment).
But objects mutating is completely different to assignment.

In what practical way? I mean, within the Ruby internals, you're
probably right. If calling it a method instead of an assignment is
only to help me remember that, being a method, it could do something
else, OK, maybe that's helpful, sort of like remembering in C++ that I
could overload (I think) the = operator.
var = expr

means calculate the value of 'expr' (which *always* results in a
reference to some object), and store this reference in the local
variable 'var' (which is a slot on the stack)

Anyway, we don't have to go any further on this point. I'm taking that
free online Ruby course, maybe I'll learn something to change my mind
there. If not, I'll just consider it an assignment till something
bites me--then I'll no doubt (I hope) learn something. ;-)
This is something I find great about Ruby: everything is a reference. In
C you have to decide whether you are passing a value or a pointer; in
C++ you have to decide whether you are passing a value, a pointer or a
reference :-(

Yes, that seems helpful.
a = malloc(123); /* a points to some memory */
c = a; /* c points to the same memory */

Ok, is there still some magic here?. Compare (thinking in C):

Given:

char *a;
char *c;

Would both of these assignments (in C) work as they would in Ruby:

a = [1, 2, 3]
c = a

You can't do exactly that in C. One possibility is

char a[3] = { 1, 2, 3 };
char *c;

c = a;

Here a and c are not exactly the same. Arrays in C are just pointers to
memory, and you could use 'a' and 'c' in any context that a 'char *'
pointer would be expected, but whilst you can modify 'c' to point
somewhere else, you cannot modify 'a' (e.g. a++ is illegal, as is a = b)

A bit closer to Ruby is:

char *a;
char *c;

a = malloc(3);
memcpy(a, "\x01\x02\x03", 3);

c = a;

Thanks, I'll have to let some of that percolate some. ;-)
Note that in Ruby, the expression [1, 2, 3] creates a new object every
time it executes. Try for example,

10.times { puts [1, 2, 3].object_id }

The same even applies for Strings:

10.times { puts "Hello".object_id }

There are only a few cases where you get the same object each time round
(e.g. Fixnums, some Regexp literals, symbols, nil, true, false)

Ok, I guess that goes back to the discussion above--the fact that it is
a different object doesn't (usually?) make a difference in a program I
might write.

Thanks!

Randy Kramer
 
R

Randy Kramer

It accomplishes whatever it has been defined to accomplish. If you do []=
there is no assigment happening unless there's an actual, real assigmnent
in []='s method definition. It's not different from any other method in that
regard. Consider this:
class Foo
def []=(x,y)
# Do nothing at all
end
end
Foo.new[1]=2 # No assigment whatsoever has taken place.

Sebastien,

Thanks, I think it does, but it still doesn't influence me to think of
it as a method, or to think it's of any practical difference to an
assignment.

In C++, I could overload the = operator (I think) to do something
different than assignment--if I did, I'd just confuse myself (at least
in most circumstances). In Ruby, somebody could make the []= method do
something other than assignment, but that would just be confusing (in
most circumstances), so I'd try to avoid it.

Randy Kramer
 
B

Brian Candler

Randy said:
In C++, I could overload the = operator (I think) to do something
different than assignment

However, you can't in Ruby. "x = y" cannot be made to mean anything
other than "evaluate the expression 'y', and store the resulting
reference in x" (*)
--if I did, I'd just confuse myself (at least
in most circumstances). In Ruby, somebody could make the []= method do
something other than assignment, but that would just be confusing (in
most circumstances), so I'd try to avoid it.

Here's an example in the standard library of where []= is clearly not
performing an assignment:

irb(main):001:0> str = "abcdefgh"
=> "abcdefgh"
irb(main):002:0> str["cde"]="x"
=> "x"
irb(main):003:0> str
=> "abxfgh"

You can see that here it's a string slicing operation, a mutation of the
string.

In any case, it's nothing to do with the assignment operator. Note that:

x = y # *is not* a method call, and DOES NOT change ANY object
(*)
x[y] = z # *is* a method call, and CAN change object(s)

I don't think I can put it any clearer than that. These really are
completely different. Also, since "x = y" involves no method call, it's
not possible to overload it or change its meaning in any way.

Aside: the [] operator may *look* like looking up an element in an
array, but it is very common for it to be used as something else.

m = lambda { |x,y| x+y }
puts m[3,4] # prints 7

HTH,

Brian.

(*) Note that 'y' could be a local variable, but it could be a method
call.

def y
"hello"
end
x = y
puts x
y = 123
x = y
puts x

And therefore if it's a method call, invoking that method *could* modify
objects as a side-effect.

Whether a bare "y" is a method call or a local variable depends on a
static, parse-time decision. If an *assignment* to a local variable y
was parsed earlier in this scope, then it's a local variable. Note:
"parsed" - not executed. So:

def foo
"hello"
end

if false
foo = "goodbye"
end
puts foo # prints nil

# But you can force the method call interpretation:
puts self.foo
puts foo()

I mention this now because it hopefully it reinforces the need to
recognise a true assignment, versus something which is actually a method
call.
 
B

Brian Candler

Randy said:
Hmm, don't know how to express this properly (and maybe it's way off
base), but are you hinting at the idea that if I were to try []= on an
immutable data type, then the behavior would not be an assignment.

[]= is *never* an assignment.

If you call []= on an object which is immutable, then by definition it
cannot mutate. But there was never any requirement for it to have a
method called []= in the first place, and even if it did, there was no
requirement for it to mutate.

Sure, I can write:

irb(main):001:0> str = "hello"
=> "hello"
irb(main):002:0> str.freeze
=> "hello"
irb(main):003:0> str["h"] = "HHH"
TypeError: can't modify frozen string
from (irb):3:in `[]='
from (irb):3
from :0

but that error is nothing to do with the method being called []=; it's
simply because the string is frozen. I get the same if I do:

irb(main):004:0> str << "foo"
TypeError: can't modify frozen string
from (irb):4:in `<<'
from (irb):4
from :0

But I *can* write:

irb(main):005:0> str = "goodbye"
=> "goodbye"

*That's* an assignment. str now points to a completely different object.
The fact that the old string was frozen is irrelevant. It will be
garbage-collected later, if no-one else also has a reference to it.
(I'm
not sure what it would do--report an error, create a new object with a
different object_id? (Some quick experiments with a constant seem to
indicate that is the case (actually, it does both).)

Or it could do nothing. Depends what you define []= to do. If no []=
method has been defined, then you'll get an "undefined method" error.

irb(main):002:0> class Foo
irb(main):003:1> end
=> nil
irb(main):004:0> Foo.new[3] = 4
NoMethodError: undefined method `[]=' for #<Foo:0xb7dddecc>
from (irb):4
from :0

Note that you'll get this error whether or not Foo has a "[]" method.
They're entirely independent.
irb(main):224:0> Const = 3.14
(irb):224: warning: already initialized constant Const
=> 3.14
irb(main):225:0> Const.object_id
=> -606494436
irb(main):226:0> Const = 3.00
(irb):226: warning: already initialized constant Const
=> 3.0
irb(main):227:0> Const.object_id
=> -606517176

You've observed that 3.00 and 3.14 are different objects.

"Const" is not an object. At first, Const held a reference to 3.14; and
then it held a reference to 3.00.

That *is* an assignment to Const. However, "Const" itself is not an
object, it's a constant. The name "constant" suggests it shouldn't
change, which is why you get a warning when you assign something
different to it.
Ok, I guess that goes back to the discussion above--the fact that it is
a different object doesn't (usually?) make a difference in a program I
might write.

Here's a case where it does:

# version 1
data = []
10.times { |i| data = "hello" }
data[0] << "x"
p data

# version 2
str = "hello"
data = []
10.times { |i| data = str }
data[0] << "x"
p data

It does catch people out when they write, e.g.

a = Array.new(10, [])

when what they really want is

a = Array.new(10) { [] }

Try both these examples, printing out the object_id of each of the 10
elements of the array, to see how they are different.
 
B

Brian Candler

Just one other thing. All this talk about []= has overlooked the much
more common case of methods called "something=".

Often they happen to perform assignment to an instance variable, e.g.

class Foo
def bar
@bar
end
def bar=(x)
@bar = x
end
end
f = Foo.new
f.bar = 99 # a method call to "bar="
puts f.bar

(There's the "attr_accessor" method to help with this common pattern).

However there's no requirement for them to do so. For example, if you
have a database connection manager class, then you might legitimately
write:

def autocommit=(flag)
if flag
@socket.write("set autocommit on;\n")
else
@socket.write("set autocommit off;\n")
end
end
...
conn.autocommit = true

This is changing state of the connection, but doesn't assign to anything
in Ruby.

Now, there is a subtle trap which I still fall into from time to time.
For example, later in the database connection manager class, you may
write another method which looks like this:

def transaction(sql)
autocommit = false
@socket.write("begin\n")
@socket.write(sql)
@socket.write("commit\n")
ensure
autocommit = true
end

This runs, but unfortunately doesn't do what was intended. "autocommit =
false" just brings a local variable into existence, and sticks false in
it. To call the method called "autocommit=" you have to write:

self.autocommit = false
...
self.autocommit = true

That is, you must always qualify these sorts of method calls with a
receiver, because an unqualified expression of the form "x = ..." is
always taken to be an assignment to a local variable x.

Anyway, just another example of why it's important to distinguish an
assignment from something that isn't :)

B.
 
R

Randy Kramer

However, you can't in Ruby. "x = y" cannot be made to mean anything
other than "evaluate the expression 'y', and store the resulting
reference in x" (*) ...
In any case, it's nothing to do with the assignment operator. Note that:

x = y # *is not* a method call, and DOES NOT change ANY object
(*)
x[y] = z # *is* a method call, and CAN change object(s)

Ok, I think this makes sense to me. Trying to repeat for myself:

* a method (is applied to and) can change an object

* an assignment (evaluates an expression and) stores a reference (to
that expression) in a variable

I'll try to let that sink in ;-)
I don't think I can put it any clearer than that. These really are
completely different. Also, since "x = y" involves no method call, it's
not possible to overload it or change its meaning in any way.
OK.

Aside: the [] operator may *look* like looking up an element in an
array, but it is very common for it to be used as something else.

m = lambda { |x,y| x+y }
puts m[3,4] # prints 7

HTH,

Brian,

Yup, thanks, I think I'm getting there--I appreciate your patience.
I'll reread all your emails a few times over the next little while.
(*) Note that 'y' could be a local variable, but it could be a method
call.

def y
"hello"
end
x = y
puts x
y = 123
x = y
puts x

And therefore if it's a method call, invoking that method *could* modify
objects as a side-effect.

Whether a bare "y" is a method call or a local variable depends on a
static, parse-time decision. If an *assignment* to a local variable y
was parsed earlier in this scope, then it's a local variable. Note:
"parsed" - not executed. So:

def foo
"hello"
end

if false
foo = "goodbye"
end
puts foo # prints nil

# But you can force the method call interpretation:
puts self.foo
puts foo()

I mention this now because it hopefully it reinforces the need to
recognise a true assignment, versus something which is actually a method
call.

Interesting!

Randy Kramer
 
R

Randy Kramer

Here's a case where it does:

# version 1
data = []
10.times { |i| data = "hello" }
data[0] << "x"
p data

# version 2
str = "hello"
data = []
10.times { |i| data = str }
data[0] << "x"
p data


Wow! That's either amazing or frightening (or both).

I did use:

10.times { |i| puts data.object_id}

... to see the object_id of each element in the arrays both above and
below.

(For the record, version 1 creates 10 different objects, version 2
creates only a single object.)
It does catch people out when they write, e.g.

a = Array.new(10, [])

when what they really want is

a = Array.new(10) { [] }

Try both these examples, printing out the object_id of each of the 10
elements of the array, to see how they are different.

Thanks again (I think ;-)

Randy Kramer
 
R

Rick DeNatale

[Note: parts of this message were removed to make it a legal post.]

They're called "references", to distinguish them from pointers because
you can't actually use them as pointers (e.g. you can't "increment" the
pointer to point to the next area of memory, as you can in C).

They are also called references (actually MRI uses the term value, but
that's probably confusing), because they don't HAVE to be pointers.

To some extent it's implementation dependent. MRI does use pointers for most
object references, but some object references are encoded directly. In MRI,
this happens for things like Fixnums, nil, true, and false. The
implementation detects these since the low-order bit of the reference value
is set, which can be used because all objects which are referenced by
pointers need to have an even address. I hint at this in the referenced
article.

The exact implementation of references/values depends on the particular
implementation of Ruby, so it's best to just think of references as opaque
'tokens' which map 1-1 with object instances.
 
L

Li Chen

Rick said:
array. Therefore @b1 becomes, each time around the loop,
[10]
[10,10]
[10,10,10]
[10,10,10,10]

How come this loop happan? I don't want a loop here.

Because that's what you coded:

irb(main):008:1* def method2
irb(main):009:2> (method1.size).times{@b1<<10}
irb(main):010:2> return @b1
irb(main):011:2> end

$ qri Integer#times
---------------------------------------------------------- Integer#times
int.times {|i| block } => int
------------------------------------------------------------------------
Iterates block int times, passing in values from zero to int - 1.

It's not really clear what you are trying to do with your two methods,
why
not just initialize both instance variable in the intialize method?

class A
def initialize
@a1 = [1,2,3,4]
@b1 = [10][email protected] # or more clearly just [10, 10, 10, 10]
end

def method1
@a1
end

def method2
@b1
end
end
The script in the OP is a simple version of another more lenghy one.

@a1 comprise all the file names from a folder.
@b1 comprise all the file names from another folder.
the construction of @b1 is dependent on @a1.
after initialize I want to check the status of @a1 and @b1.
out of curiossity I call both method1 and method2 more than once.



If I initialize both @a1 and @b1 in initialize method there would many
lines there. In order to keep the method short and easy to read I want
to keep each step in a different method.But I also want to initialize
both of them in initialize method. This is the reason why I put method1
and method2 in initialize method. I guess I misunderstood how method is
called in Ruby. I think I can retrieve the status of @a1 and @b1 by
writing accessor/getter. This way I would not mess up with the method
call.

I didn't realize this post causes so much discussion when I posted. I
really appricate you guys spending your times and explaining the
problem.


Li
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top