In-place parameter modification

D

Dave Anderson

Native to ruby are several methods that change passed-in parameters
in-place, such as chomp!(somestring). But I don't see any info on
creating these things myself. Is it possible to write methods that
change parameters this way?
 
J

Judson Lester

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

Actually, chomp!(string) doesn't modify string, it modifies $_ (which, if I
recall correctly, is set by the -n and -p switches to ruby).

String#chomp! modifies the recipient.

Finally: ruby is a pass-by-reference language, so if you actually do make
changes the parameters of a method, the callers variables will change as
well. For instance:

def make_bacon(string)
string.replace "Chunky bacon."
end

hi = "hello"
make_bacon(hi)
hi #=> "Chunky bacon."

Judson
 
7

7stud --

Judson said:
Finally: ruby is a pass-by-reference language, so if you actually do
make
changes the parameters of a method, the callers variables will change as
well.

def test(x)
x = 10
end

num = 5
test num
puts num

--output:--
5

???
 
D

Dave Anderson

7stud said:
def test(x)
x = 10
end

num = 5
test num
puts num

--output:--
5

???

My educated guess is that string.replace("Chunky bacon") is operating on
the original, whereas string = "Chunky bacon" just makes a new local
copy. The pickaxe book suggests that this only works on Arrays, Hashes
and Strings, though, so there's no way to make replace work on a fixnum.
 
R

Robert Gleeson

Hello,

Dave said:
Native to ruby are several methods that change passed-in parameters
in-place, such as chomp!(somestring). But I don't see any info on
creating these things myself. Is it possible to write methods that
change parameters this way?

Yup, you can write your own mutating methods that mutate the
receiver(like chomp!, gsub!, sub!, and so on), but you can't mutate any
of the core classes because you cannot reassign self.

If it were your own class, though, you might have:

class MyString

def initialize str
@str = str
end

def append!
@str << ' ruby'
end

def to_s
@str
end

end

str = MyString.new 'I love '
str.append!
puts str # => 'I love ruby'

chomp! takes one argument, and that is a seperator(or what to remove
from the end of the string)

Hope this helps some,
Rob
 
J

Jordi Bunster

Yup, you can write your own mutating methods that mutate the
receiver(like chomp!, gsub!, sub!, and so on), but you can't mutate
any
of the core classes because you cannot reassign self.

Some core classes know how to replace themselves:

class String
def clobber!
replace ''
end
end

I wonder why Object (or BasicObject) doesn't know this trick.

I also wonder why "replace" isn't called "replace!" :)
 
R

Robert Gleeson

Jordi said:
Some core classes know how to replace themselves:

class String
def clobber!
replace ''
end
end

I wonder why Object (or BasicObject) doesn't know this trick.

I also wonder why "replace" isn't called "replace!" :)

This is cool, but it only works for mutating methods that already exist,
if you want to write your own you're out of luck, as far as the core
classes go.
 
D

David A. Black

Hi --

Some core classes know how to replace themselves:

class String
def clobber!
replace ''
end
end

I wonder why Object (or BasicObject) doesn't know this trick.

I don't think "replace" has any generalizable semantics. You can't
replace 2 with 3, for example (fortunately :)
I also wonder why "replace" isn't called "replace!" :)

The !/non-! methods come in pairs. The ! one is the "dangerous" one --
that is, the one that probably has side-effects, special-case
semantics, etc. A lot of the "danger" involves changing the receiver,
but not all of it (e.g., exit!).

Therefore, if it's just one method, not a pair, it simply has a name
that says what it does, without a !. String#replace has to be
destructive. Otherwise the name is absurd. (str.replace("abc") can be
better written as just "abc", if all it's doing is creating a
completely new string with the characters "abc".)

There's no one-to-one correspondence, in either direction, between
receiver-changing methods and the !. Array#pop and friends, the
various replace and clear methods, Hash#update -- there are lots of
them.

Here's some further reading on the !-convention (and its lamentable
near-disappearance):

http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist


David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

David A. Black

Hi --

My educated guess is that string.replace("Chunky bacon") is operating on
the original, whereas string = "Chunky bacon" just makes a new local
copy. The pickaxe book suggests that this only works on Arrays, Hashes
and Strings, though, so there's no way to make replace work on a fixnum.

That's a good thing :)


David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
 
A

Aria Stewart

This is cool, but it only works for mutating methods that already
exist,
if you want to write your own you're out of luck, as far as the core
classes go.

Unless you hack in C or use evil.rb.
They're just C structs, ultimately, and you can do ALL sorts of fun
with them.

Immediates could be tricky, though, since the reference to them /is/
the data. No struct in sight.
 
R

Rick DeNatale

def test(x)
=A0x =3D 10
end

num =3D 5
test num
puts num

--output:--
5

???

As long as you've been hanging out here, I'm surprised if you are
really surprised by this.

In order to get completely comfortable with Ruby you need to
understand the relationships and differences between objects and
variables

Objects are instances of classes, and typically have state, in some
cases like Fixnums the only state is really the object's identity, and
such objects are immutable, if two such objects have different state,
then they MUST be different objects. There is only one 1 object and
one 5 object. Symbols are similar in that there is only one instance
of symbol with a particular value.

Some clases have methods which change the state of an object without
changing it's identity. String.gsub! is one such method. You can
write such mutating methods yourself, clearly for your own classes,
but also for core classes which already have mutating methods by
writing new methods interms of the existing mutating methods.

A variable is not an object, rather it is a temporary binding to a
particular object which is established by, and can be changed by an
assignment expression, syntactic sugar which disguises an assignment
expression (e.g. x +=3D y). Variables include locals, method
parameters, instance variables, element slots in arrays, and block
arguments (which are distinguished from local variables in Ruby 1.9).

So variables can't be mutated, they can only have their binding
switched to another variable, if two variables reference the same
object, and the binding of one of those variables changes to reference
another object, the other variable is unaffected.


def test(x)
x =3D 10
end

num =3D 5
test num
puts num

--output:--
5
But, again since variables are bindings to objects, if two variables
reference the same object, and that object is mutated, then the change
in state will by visible through ANY reference to that particular
object.


x and num are two different variables. At the time test is called,
num is bound to 5, inside the context of the invocation of test, x
starts out bound to 5, then the assignment changes the binding of x
to 1, and leaves the binding of num alone. Imagine the code had been:

num=3D5
x =3D 1
puts num
output:
5

I hope that this is completely unsurprising, I suspect the surprise
might come from the assumption that somehow x and num are the same
variable because of the formal/actual parameter pairing.


In the case of method arguments (and block arguments in Ruby 1.9) the
name is local to the method (or block). This means that x and num are
indeed not the same variable, but two variables one of which (x) is
initialized by the call to refer to the same object resulting from the
expression used in the call, which in this cases happen to simply be
the variable num.

Imagine that we'd called

test(num + 1)

Here the actual value would be not 5 but 6, and there's no visible
variable which corresponds to the actual expression.

And even if the FORMAL parameter name were changed:

def test(num)
num =3D 10
end

num =3D 5
test num
puts num

--output:--
5

The result would be the same, since the names num at the top level,
and num inside the test method are two different variables.

HTH

And of course there's my oldie but goodie:

http://talklikeaduck.denhaven2.com/2006/09/13/on-variables-values-and-objec=
ts



--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
D

Dave Anderson

Rick Denatale wrote:
...
Objects are instances of classes, and typically have state, in some
cases like Fixnums the only state is really the object's identity, and
such objects are immutable, if two such objects have different state,
then they MUST be different objects. There is only one 1 object and
one 5 object. Symbols are similar in that there is only one instance
of symbol with a particular value.

Thanks for the fascinating and valuable insights, Rick. I didn't realize
what a can of worms I was opening here. And it's nice to know that
someone here remembers Fortran II, although that might make you a good
candidate for a Men-In-Black neurolizer. ;-) As an old hand at C++, this
variable/value/type stuff is a very worthwhile lesson.

One practical thing I can draw out of this is the difference between
these two functions:

def X_1( str )
str[0] = 'X'
end

def X_2( str )
str = 'X' + str[ 1..-1 ]
end

The X_1 function alters the passed-in object in place, while the X_2
function modifies a local copy. I don't recall reading this in the
literature anywhere.
 
J

Jordi Bunster

I don't think "replace" has any generalizable semantics. You can't
replace 2 with 3, for example (fortunately :)

You could just memcpy a to b (as long as their classes are eql?). If
for a given object it doesn't make sense, like for singleton ones,
then you don't allow it.

I wonder if they would take a patch. Is not as if Ruby doesn't already
have a ton of edge cases anyway.
The !/non-! methods come in pairs. The ! one is the "dangerous" one --
that is, the one that probably has side-effects, special-case
semantics, ...

Ok, that makes sense now.
 
7

7stud --

Dave said:
Rick Denatale wrote:
...
Objects are instances of classes, and typically have state, in some
cases like Fixnums the only state is really the object's identity, and
such objects are immutable, if two such objects have different state,
then they MUST be different objects. There is only one 1 object and
one 5 object. Symbols are similar in that there is only one instance
of symbol with a particular value.

Thanks for the fascinating and valuable insights, Rick. I didn't realize
what a can of worms I was opening here. And it's nice to know that
someone here remembers Fortran II, although that might make you a good
candidate for a Men-In-Black neurolizer. ;-) As an old hand at C++, this
variable/value/type stuff is a very worthwhile lesson.

One practical thing I can draw out of this is the difference between
these two functions:

def X_1( str )
str[0] = 'X'
end

def X_2( str )
str = 'X' + str[ 1..-1 ]
end

The X_1 function alters the passed-in object in place,

Remember that in ruby "[]=" is the name of a method:
str[0] = 'X'

And a method can be programmed to alter the original string and return
it; or it can create a new string that is a copy of the original and
return the altered copy.
I don't recall reading this in the literature anywhere.

$ri String#[]=
------------------------------------------------------------- String#[]=
str[fixnum] = fixnum
str[fixnum] = new_str
str[fixnum, fixnum] = new_str
str[range] = aString
str[regexp] = new_str
str[regexp, fixnum] = new_str
str[other_str] = new_str
------------------------------------------------------------------------
Element Assignment---***Replaces some or all of the content of _str_
(that should read 'str' in my opinion).***

while the X_2
function modifies a local copy.

With a basic assignment statement like this:
str = 'X' + str[ 1..-1 ]

'=' is not the name of a String method. Instead:

---
This form of assignment is hardwired into the language.
---
(pickaxe2, p. 90)


And that type of assignment in ruby works like this:

If you write this:

x = 10

you get this:

x --> 10

In other words, x refers to 10. If you then write:

y = 10

you get:

x-----> 10
^
|
y ------+


Then if you write:

y = 20

you do not get:

x-----> 20
^
|
y ------+

Instead you get:

x ----> 10

y ----> 20

I like to think of it like this: with that basic type of assignment you
take the variable name on the left of the assignment, 'y' in this case,
and write it on a stick-it note, and paste the stick-it note on the
object on the right, the object 20. If ruby has already written the
same variable name on a stick-it note and pasted it on some other object
prior to your assignment statement(in the same scope), then ruby tears
the stick-it note off the other object and pastes it on the object 20.
 
7

7stud --

Because you mentioned C++, I'll just point out that "ruby references"
are not like C++ reference types. In C++, a reference variable is an
alias for another variable, which means that both variables refer to the
same location in memory. If you use one of the variable names to change
what is at that location in memory, the other variable, because it
retrieves its value from the same location in memory, also refers to the
changed value.

In my ruby example above, x and y are not aliases--even though they
refer to the same object 10. As a result, when y is assigned a new
value, it does not affect x.
 
D

David A. Black

Hi --

You could just memcpy a to b (as long as their classes are eql?). If for a
given object it doesn't make sense, like for singleton ones, then you don't
allow it.

I wonder if they would take a patch. Is not as if Ruby doesn't already have a
ton of edge cases anyway.

The "There's a precedent" argument can always be inverted, though,
into the "We've reached our quota" argument :) Of course, that's
where Matz and his wonderful sense of balance come in.

In any case, I wouldn't call such a method "replace", because it's
radically different from the existing replace methods. Somewhere
there's at least one implementation (maybe in evil.rb) of a method
called "become", which I think does what you're describing, or
something similar: a.become(b) means that all existing references to a
become references to b, if I'm remembering/summarizing correctly.


David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
 
R

Rick DeNatale

One practical thing I can draw out of this is the difference between
these two functions:

=A0def X_1( str )
=A0 =A0str[0] =3D 'X'
=A0end

=A0def X_2( str )
=A0 =A0 str =3D 'X' + str[ 1..-1 ]
=A0end

The X_1 function alters the passed-in object in place, while the X_2
function modifies a local copy. I don't recall reading this in the
literature anywhere.

Actually, the X_2 function doesn't make a local copy of the string
passed as the argument.

It ends up creating three new strings.

1 A copy of the literal string 'X'
2 A string which is the result of str[1..-1]
3 A string which is the concatenation of strings 1 and 2 which is then
bound to the local variable str. and will be the result of the method
call since it's the last expression and the value of an assignment
expression is the right hand side of the assignment.


--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 

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
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top