Passing script input as method args always global?


#1

In Perl I used to do this:

print 'Enter data: ';
chomp(my $in = );

sub doit {
my $data = shift;

return $data;
}

doit($in);

In Ruby the equivalent seems to be:

puts 'Enter data: ’
in = gets.chomp

def doit(data)

return data
end

doit(in)

However, Ruby passes arguments by reference so it seems the variable
‘data’ is always accessing and changing the file-global variable ‘in’.
Is there a more Perl-like, way of lexicalising the scope of variables
passed as arguments to functions?

gvim


#2

gvim removed_email_address@domain.invalid wrote:

However, Ruby passes arguments by reference so it seems the variable
‘data’ is always accessing and changing the file-global variable
‘in’. Is there a more Perl-like, way of lexicalising the scope of
variables passed as arguments to functions?

Make a copy of the argument when you call the method:

doit(in.dup)


#3

On 13/11/2013 01:57, Eric W. wrote:

Make a copy of the argument when you call the method:

doit(in.dup)

I seem to have misunderstood something. Changing ‘data’ within the
‘doit’ method does not seem to alter the ‘in’ variable even though it is
passed as an argument to the method. So it seems args are passed by
value after all … unless I’m mistaken … again.

gvim


#4

On Tue, Nov 12, 2013 at 9:10 PM, gvim removed_email_address@domain.invalid wrote:

So it seems args are passed by value after all … unless I’m mistaken

… again.

Args are always passed by reference. When you pass in to doit, data
and inpoint to the same string object.

Without seeing what else goes on inside doit, its hard to offer insight
on
the behavior youre seeing.


#5

Gerald Vim wrote in post #1127104:

On 13/11/2013 01:57, Eric W. wrote:

Make a copy of the argument when you call the method:

doit(in.dup)

I seem to have misunderstood something. Changing ‘data’ within the
‘doit’ method does not seem to alter the ‘in’ variable even though it is
passed as an argument to the method. So it seems args are passed by
value after all … unless I’m mistaken … again.

gvim

No, you were right to start with, ruby passes by reference. However,
Ruby often implements copy-and-modify (as opposed to destructive
modify-in-place) methods; as a general rule: if the method ends with an
exclamation mark ! it modifies the object in place, and if not, it
doesn’t (except for some that do).

— 8< —
irb(main):001:0> foo = gets.chomp
Hello Matty!
=> “Hello Matty!”
irb(main):002:0> def doit(data) data.gsub!(/(.)\1+/){ $1 }; end
=> nil
irb(main):003:0> doit foo
=> “Helo Maty!”
irb(main):004:0> foo
=> “Helo Maty!”
— >8 —

Note how in the `doit’ method I called gsub! (which modifies the
receiver in place), and not gsub (which creates a copy first).

There’s also a difference between ‘modifying the object’ and ‘updating
the variable to refer to a different object’. If I did:

def doit(data)
data = “The data is #{data}”
#…
end

that would be creating a new String object, then assigning that to the
variable called data'. It would not be the same as modifying the characters inside the originaldata’ String object.


#6

On Nov 12, 2013, at 7:09 PM, gvim removed_email_address@domain.invalid wrote:

return data
end

doit(in)

However, Ruby passes arguments by reference so it seems the variable ‘data’ is
always accessing and changing the file-global variable ‘in’. Is there a more
Perl-like, way of lexicalising the scope of variables passed as arguments to
functions?

gvim

The thing Ive come to in my own understanding is that since everything
is an object in Ruby, that means every variable is actually a pointer to
an object in Ruby as well.

So when you pass in to the doit method, you are passing the reference
into the local method variable data. Initially, data and in are pointing
to the same object. But then you have to be aware of what things you are
doing on the data variable. If its things with modify the same object
originally point to by in, then the object in points to is being
changed. Thus it looks like in is changed.

Doing other things can create new objects in ruby, though, so doing
something to data that ends up creating a new object also ends up
making in look unchanged when doit exits, which is so.

So lets try this.

def doit2(data)
data.shuffle
end

According to the docs, shuffle creates a new array. So:

$ pry
[1] pry(main)> start_data = [1,2,3,4,5].freeze
=> [1, 2, 3, 4, 5]
[2] pry(main)> a_ary = start_data
=> [1, 2, 3, 4, 5]
[3] pry(main)> def doit(data)
[3] pry(main)* data.shuffle
[3] pry(main)* end
=> nil
[4] pry(main)> doit(a_ary)
=> [2, 4, 5, 3, 1]
[5] pry(main)> a_ary
=> [1, 2, 3, 4, 5]
[6] pry(main)> a_ary.object_id
=> 70307654300780
[7] pry(main)> doit(a_ary).object_id
=> 70307649978420
[8] pry(main)> doit(a_ary).object_id
=> 70307650161900

So you can see that .shuffle is creating a new object each time.

But remember we froze the source data up there? a_ary is also pointing
to the frozen array, so when you change doit to something like this:

[9] pry(main)> def doit(data)
[9] pry(main)* data.shift
[9] pry(main)* end
=> nil
[10] pry(main)> a_ary
=> [1, 2, 3, 4, 5]
[11] pry(main)> a_ary.object_id
=> 70307654300780
[12] pry(main)> b_ary = doit(a_ary)
RuntimeError: can’t modify frozen Array
from (pry):12:in `shift

It fails because we tried to change a frozen object.

Now if we do this:

[13] pry(main)> b_ary = doit(a_ary.dup)
=> 1
[14] pry(main)> a_ary
=> [1, 2, 3, 4, 5]
[15] pry(main)>

doit is working on a new object that isnt frozen.


#7

On 13/11/2013 02:23, Avdi G. wrote:


#8

Subject: Re: Passing script input as method args always global?
Date: mer 13 nov 13 11:18:48 +0000

Quoting gvim (removed_email_address@domain.invalid):

puts var #=> 2
The line

x = x + 1

creates a new object. You can see this by printing the object id of x
before and after the sum:


def f(x)
puts(“before: #{x.object_id}”)
x = x + 1
puts(“after: #{x.object_id}”)
return x
end

gives here:

before: 5
after: 7

Carlo


#9

Ruby passes arguments by value. You can modify a mutable object via
its
API, but that has nothing to do with call semantics.


#10

On 13/11/2013 11:48, Xavier N. wrote:

Ruby passes arguments by value. You can modify a mutable object via
its API, but that has nothing to do with call semantics.

Thank you! That’s the only explanation that makes sense so far but
others seem to disagree.

gvim


#11

I think you are passing a copy of the reference to the object. This is
why
you can manipulate the object itself, but you can also point that
reference
to another object, without affecting the original.


#12

On Wed, Nov 13, 2013 at 2:09 AM, gvim removed_email_address@domain.invalid wrote:

In Perl I used to do this:

doit($in);
end

doit(in)

However, Ruby passes arguments by reference so it seems the variable
‘data’ is always accessing and changing the file-global variable ‘in’. Is
there a more Perl-like, way of lexicalising the scope of variables passed
as arguments to functions?

Perl only has pass by reference (the elements of @_ are aliases of the
ones
in the caller), and Ruby has only pass by value.

But, Perl has a number of basic types like arrays that you handle
directly:

@a = (1);

is an array, and

$x = \@a;

is a reference to an array.

In Ruby you basically only have the latter. And since that’s the only
thing
you have no arrow is needed to dereference, you are always dealing with
reference values so there is no extra syntax. That is, in Ruby

array[0]

translates to Perl as:

$arrayref->[0]

Therefore, if you are going to mutate the string in the doit method and
do
not want the caller to see the mutation you need to clone the argument:

def doit(data)
  data = data.dup
  ...
end

I personally prefer to clone in the method rather than in the caller. My
caller code should not be just paranoid and defensive, on the other hand
doit knows he is going to mutate, and knows it is not a good
practice
to mutate arguments, so he is responsible for cloning in my view.

Xavier


#13

Subject: Re: Passing script input as method args always global?
Date: mer 13 nov 13 12:01:24 +0000

Quoting gvim (removed_email_address@domain.invalid):

So a parameter is passed by reference but as soon as you modify it
you’ve cloned the object? Then what’s the point passing it by
reference in the first place?

It is not always true that the object is cloned. It depends on the
method. See this example:

class Tally
attr_reader(:v)

def initialize(v)
@v=v
end

def +(v)
@v+=v
self
end

def to_s
@v.to_s
end
end

var=Tally::new(2)

def f(x)
x=x+1
return x
end

puts f(var) #=> 3
puts var #=> 3

Carlo


#14

On 13/11/2013 11:32, Carlo E. Prelz wrote:

x = x + 1
Carlo

So a parameter is passed by reference but as soon as you modify it
you’ve cloned the object? Then what’s the point passing it by reference
in the first place?

gvim


#15

On Wed, Nov 13, 2013 at 1:01 PM, gvim removed_email_address@domain.invalid wrote:

So a parameter is passed by reference but as soon as you modify it
you’ve

cloned the object? Then what’s the point passing it by reference in the
first place?

There is some terminology going on here. I am going to explain it below
for
completeness, but the main issue in this thread is that you understand
that
Ruby passes a reference to the string, as if you hace a reference to a
scalar in Perl.

So, you need to think in terms of references always in Ruby. In Perl, if
you pass a reference to an array a subroutine can change the array in
the
caller via the reference, right? That’s always the case in Ruby because
there are always references going on (conceptually, there are some
exceptions in the implementation of MRI but that’s not relevant).

Now for the jargon. Passing “a reference” to a method, is not the same
as
“passing by reference” calling semantics.

In Perl, you can do this (written off the top of my head):

sub foo {
  $_[0] = 1;
}

$a = 0;
foo($a)
print "$a\n"; # => 1

because $a is passed by reference. $_[0] and $a are containers that
point
to the same scalar (think symbol tables).

In pass by reference semantics you can write swap this way (pseudocode):

a = 1
b = 2
def swap(a, b)
  b, a = a, b
end
a == 2 # => true
b == 1 # => true

That’s the idea. Perl implements pass by reference, and Ruby pass by
value.
Ruby passes references to objects by value.


#16

Hey guys

How do I unsubscribe? Or reduce the the mailings to a digest?

Thanks

amichai


Sent from Mailbox for iPhone


#17

Subject: Re: Passing script input as method args always global?
Date: mer 13 nov 13 12:14:36 +0000

Quoting gvim (removed_email_address@domain.invalid):

You have
used ‘v’ in 3 different contexts here - :v, @v and (plain old)v.
This is what I find so confusing about Ruby, along with this
parameter passing.

No! :v, v and @v are THREE DIFFERENT THINGS altogether.

:v is a symbol that, where it is used, references the name of an
instance variable

@v is the instance variable (local to the class instance, and thus
maintaining its value for the life of the instance)

v is a local variable whose scope is limited to each of the two
methods where it is used.

Carlo


#18

On 13/11/2013 12:10, Carlo E. Prelz wrote:

 self

x=x+1
return x
end

puts f(var) #=> 3
puts var #=> 3

Carlo

I don’t see the connection with my simple function example. You have
used ‘v’ in 3 different contexts here - :v, @v and (plain old)v. This is
what I find so confusing about Ruby, along with this parameter passing.
Can you use an example without classes?

gvim


#19

On 13/11/2013 12:20, Carlo E. Prelz wrote:

Carlo

I know they’re different. It’s just that my example was simplified to
try to work out what’s going on but your use of a class with 3 different
variable types doesn’t clarify anything.

gvim


#20

On 13/11/2013 12:13, Xavier N. wrote:

Which is what I would expect, but why does x retain its original value
in this example:

x = 2

def f(y)
y = y + 1
return y
end

puts f(x) #=> 3
puts x #=> 2

In your example a and b are mutated but in mine x remains unchanged.

gvim