Method Chaining Issues

A

aartist

try this:
string = "I am sold"
string.sub!('old','new').reverse!;
puts string

It works.

If string didn't contain the word 'old' then it will fail.
Is that a nice behavior?
Can it be changed to work on whatever input?
 
R

Ralph \PJPizza\ Siegler

try this:
string = "I am sold"
string.sub!('old','new').reverse!;
puts string

It works.

If string didn't contain the word 'old' then it will fail.
Is that a nice behavior?
Can it be changed to work on whatever input?

Maybe this just shows that in-place modifying methods that can potentially return nil or an object without needed methods are too dangerous to chain for bullet-proof applications - in this case use .sub and .reverse with tests along the way
 
D

Dan Fitzpatrick

aartist said:
try this:
string = "I am sold"
string.sub!('old','new').reverse!;
puts string

It works.

If string didn't contain the word 'old' then it will fail.
Is that a nice behavior?
Can it be changed to work on whatever input?
You can remove the nil case like this:

(string.sub!('x','new') || string).reverse!

Dan
 
P

Phrogz

This is a FAQ, though no page on the RubyGarden wiki seems to address
it.

Some kind soul should search the archives for all the threads and RCRs
surrounding this topic and create a page on the wiki with links, so we
can redirect people to it.

In short - some people think it's nice that the 'bang' methods return
nil if no change was effected, some people do not. You can either chain
them using intermediary strings:

string.sub('old','new').reverse

or you can use the bang methods, but not use chaining

string.sub!('old','new')
string.reverse!
 
N

Nikolai Weibull

Phrogz said:
Some kind soul should search the archives for all the threads and RCRs
surrounding this topic and create a page on the wiki with links, so we
can redirect people to it.

In short - some people think it's nice that the 'bang' methods return
nil if no change was effected, some people do not. You can either chain
them using intermediary strings:

string.sub('old','new').reverse

or you can use the bang methods, but not use chaining

string.sub!('old','new')
string.reverse!

It should also be pointed out that the non-destructive version of sub
(i.e., String#sub) method often turns out to be faster than the
destructive one (i.e., String#sub!),
nikolai
 
G

Gyoung-Yoon Noh

It should also be pointed out that the non-destructive version of sub
(i.e., String#sub) method often turns out to be faster than the
destructive one (i.e., String#sub!),
nikolai
=20

No, what you said are inverted.
Some non-destructive methods are usually implemented by
simply copying reference after calling destructive method.
It looks like:

def sort(ary):
sort!(ary)
return ary
end

This is a benchmark results.

$ cat destructive.rb
require 'benchmark'

Benchmark.bmbm do |bm|
bm.report("Destructive") do
1000.times do
ary =3D (0..10000).to_a
ary.reverse!
ary.sort!
end
end
bm.report("Non-destructive") do
1000.times do
ary =3D (0..10000).to_a
ary.reverse.sort
end
end
end

$ ruby destructive.rb
Rehearsal ---------------------------------------------------
Destructive 2.910000 0.000000 2.910000 ( 2.904260)
Non-destructive 3.110000 0.000000 3.110000 ( 3.116915)
------------------------------------------ total: 6.020000sec

user system total real
Destructive 2.930000 0.000000 2.930000 ( 2.935663)
Non-destructive 3.170000 0.010000 3.180000 ( 3.171050)

--=20
http://nohmad.sub-port.net
 
S

Sam Goldman

Phrogz said:
This is a FAQ, though no page on the RubyGarden wiki seems to address
it.

Some kind soul should search the archives for all the threads and RCRs
surrounding this topic and create a page on the wiki with links, so we
can redirect people to it.

In short - some people think it's nice that the 'bang' methods return
nil if no change was effected, some people do not. You can either chain
them using intermediary strings:

string.sub('old','new').reverse

or you can use the bang methods, but not use chaining

string.sub!('old','new')
string.reverse!
Some people think that "bang" methods shouldn't exist at all!

- John Q. Haskell
 
S

Sam Goldman

Gyoung-Yoon Noh said:
No, what you said are inverted.
Some non-destructive methods are usually implemented by
simply copying reference after calling destructive method.
<snip>

While everything you said is true, a "smart enough" compiler can do some
very interesting optimizations on whole programs if it can assume the
idempotence of methods. This is one of the most attractive points of
functional programming (that and bringing math back into programming).

- Sam
 
K

Kyle Heon

Some people think that "bang" methods shouldn't exist at all!
Definitely,
Nikolai

I'm new to Ruby so I when you refer to a "bang" method you mean something
like "chomp!"? And if so, why do some thing they shouldn't exist? Just
curious.

Kyle Heon
(e-mail address removed)
 
N

Nikolai Weibull

Gyoung-Yoon Noh said:
Some non-destructive methods are usually implemented by
simply copying reference after calling destructive method.

And some aren’t. Anyway, I’ve had gsub! be a lot slower than gsub in
benchmarks. Still, some destructive methods will by their very
definition be faster.
user system total real
Destructive 2.930000 0.000000 2.930000 ( 2.935663)
Non-destructive 3.170000 0.010000 3.180000 ( 3.171050)

I’d say that a time-difference of 0.000235 seconds per call to
String#sort!.reverse! versus String#sort.reverse doesn’t warrant
destructive methods on strings (in this benchmark anyway),
nikolai
 
E

ES

Le 1/6/2005, "Nikolai Weibull"
And some aren=E2=80=99t. Anyway, I=E2=80=99ve had gsub! be a lot slower tha= n gsub in
benchmarks. Still, some destructive methods will by their very
definition be faster.


I=E2=80=99d say that a time-difference of 0.000235 seconds per call to
String#sort!.reverse! versus String#sort.reverse doesn=E2=80=99t warrant
destructive methods on strings (in this benchmark anyway),

For Strings the performance penalty is typically negligible (it
may not be so for all classes). But consider this:

class Foo
def initialize
@a =3D 5
end

attr_accessor :a
end

foo =3D Foo.new
foo.a =3D 6

Current behaviour aside, should the last line create a new Foo object?

E
 
N

Nikolai Weibull

ES said:
class Foo
def initialize
@a = 5
end

attr_accessor :a
end

foo = Foo.new
foo.a = 6

Current behaviour aside, should the last line create a new Foo object?

Very funny. Totally different argument,
nikolai
 
N

Nikolai Weibull

I'm new to Ruby so I when you refer to a "bang" method you mean
something like "chomp!"? And if so, why do some thing they shouldn't
exist? Just curious.

Because they alter the value of the object that they are invoked upon.
For many data objects, modification of the object itself is precisely
what you want, but for, for example, strings, this is most often not the
case. Bang methods cause more confusion than they are worth, as has
been shown over and over on this list. I have no problem with
Hash#delete, mind you, but String#gsub! can easily be substituted by
String#gsub without worrying about time penalties. And for cases where
you might worry, you shouldn’t be using a String in the first place.

Bang methods are often a sign of premature optimization, which is often
considered the number one no-no in programming,
nikolai (who’s sure that other people’ll fill in the other
arguments…for and against)
 
N

Nikolai Weibull

ES said:
This is the counterargument for keeping destructive methods.

No it’s not. This is an argument for keeping Ruby non-functional. This
has nothing to do with String#sub vs. String#sub! or Array#sort vs.
Array#sort! for that matter. When discussing destructive methods with
bangs, we’re discussing the merit of having String#sub! around for
efficiency reasons. It’s not a discussion of whether to allow objects
to modify their internal state or not. I know you know this and I know
what you’re trying to say, but this doesn’t lead anywhere,
nikolai
 
G

Gyoung-Yoon Noh

And some aren't. Anyway, I've had gsub! be a lot slower than gsub in
benchmarks. Still, some destructive methods will by their very
definition be faster.
=20
I'd say that a time-difference of 0.000235 seconds per call to
String#sort!.reverse! versus String#sort.reverse doesn't warrant
destructive methods on strings (in this benchmark anyway),
=20

OK, you're right in case of String#gsub and String#gsub!.
But not 'a lot slower' but 'a little slower'.

$ cat destructive.rb
require 'benchmark'

Benchmark.bmbm do |bm|
bm.report("Destructive") do
100.times do
str =3D (0..10000).to_a.join('-')
str.gsub!(/\d+/, '-')
end
end
bm.report("Non-destructive") do
100.times do
str =3D (0..10000).to_a.join('-')
str =3D str.gsub(/\d+/, '-')
end
end
end

$ ruby destructive.rb
Rehearsal ---------------------------------------------------
Destructive 4.240000 0.010000 4.250000 ( 4.448997)
Non-destructive 4.250000 0.010000 4.260000 ( 4.279959)
------------------------------------------ total: 8.510000sec

user system total real
Destructive 4.260000 0.010000 4.270000 ( 4.378810)
Non-destructive 4.230000 0.010000 4.240000 ( 4.305321)

I don't understand what exactly this difference implies.
Does that mean Ruby's String is much alike raw C string?
Or is there any particular optimization for String copy?
Anyway, I don't like early optimization too in your sense.
But in most case except String, bang method will be much
faster than normal methods which return new value.
This difference is somewhat meaningful, at least, than your case.
I have a strong affection on method chaining, so mostly
I don't use bang method. But I should admit that bang version of
same method would be somewhat faster.

--=20
http://nohmad.sub-port.net
 
E

ES

Le 2/6/2005, "Nikolai Weibull"
No it=E2=80=99s not. This is an argument for keeping Ruby non-functional. = This
has nothing to do with String#sub vs. String#sub! or Array#sort vs.
Array#sort! for that matter. When discussing destructive methods with
bangs, we=E2=80=99re discussing the merit of having String#sub! around for
efficiency reasons. It=E2=80=99s not a discussion of whether to allow objec= ts
to modify their internal state or not. I know you know this and I know
what you=E2=80=99re trying to say, but this doesn=E2=80=99t lead anywhere,

It is relevant to the discussion on whether the bang-methods
should be deprecated (that was the topic). Efficiency usually
does not factor in apart from high-volume transactions, so the
only remaining valid argument for them is a conceptual one.

If I want to modify a String, I want to modify it, not create a
new one and assign it to the space of the old one. The conceptual
benefit of this is, of course, more prominent in contexts other
than Strings as shown by the Foo example.

E
 
D

Devin Mullins

Nikolai said:
It’s not a discussion of whether to allow objects
to modify their internal state or not.
Seeing how Strings are objects, I'm confused, then. You're only talking
about builtin objects? You're only talking about objects that have
"literal" counterparts (String, Array, Hash, Symbol, etc.)? You're only
talking about objects that end in "tring"?

While I almost always opt for the nondestructive methods, I don't feel
like making that decision for others.

That said, I don't really care, but I thought I'd try and get you both
on the same page.

Devin
 
E

Eric Hodel

OK, you're right in case of String#gsub and String#gsub!.
But not 'a lot slower' but 'a little slower'.

$ cat destructive.rb
require 'benchmark'

Benchmark.bmbm do |bm|
bm.report("Destructive") do
100.times do
str = (0..10000).to_a.join('-')
str.gsub!(/\d+/, '-')
end
end
bm.report("Non-destructive") do
100.times do
str = (0..10000).to_a.join('-')
str = str.gsub(/\d+/, '-')
end
end
end

$ ruby destructive.rb
Rehearsal ---------------------------------------------------
Destructive 4.240000 0.010000 4.250000 ( 4.448997)
Non-destructive 4.250000 0.010000 4.260000 ( 4.279959)
------------------------------------------ total: 8.510000sec

user system total real
Destructive 4.260000 0.010000 4.270000 ( 4.378810)
Non-destructive 4.230000 0.010000 4.240000 ( 4.305321)

The difference is not in the speed of the operations themselves, it
is in the memory pressure on the GC. Your benchmark is not a fair
comparison between the two because it ignores the side-effect of
memory pressure and does not fit with the way you would use chaining
vs !.

This benchmark more realisticly shows the effects of memory pressure
and is more fitting with how you would really use chaining vs !:

$ cat sub.rb
require 'benchmark'

N = 10_000
STR = (0..N).to_a.join('-')

Benchmark.bmbm do |bm|
bm.report("Base") do
str = STR.dup
N.times { }
end

bm.report("Destructive") do
str = STR.dup
N.times { str.sub!(/\d+/, '-') }
end

bm.report("Non-destructive") do
str = STR.dup
N.times { str = str.sub(/\d+/, '-') }
end
end

$ ruby sub.rb
Rehearsal ---------------------------------------------------
Base 0.000000 0.000000 0.000000 ( 0.003034)
Destructive 1.470000 1.380000 2.850000 ( 3.266492)
Non-destructive 1.930000 2.740000 4.670000 ( 5.584387)
------------------------------------------ total: 7.520000sec

user system total real
Base 0.000000 0.000000 0.000000 ( 0.002857)
Destructive 1.460000 1.400000 2.860000 ( 3.383933)
Non-destructive 1.910000 2.800000 4.710000 ( 5.688635)

I don't understand what exactly this difference implies.

Your benchmark wasn't helpful in revealing the true difference
between the two methods. As you can see, the non-destructive case
takes much more time in user space because the GC has to clean up all
the temporary strings.

It also takes ~33% more time in general because of the GC.
Does that mean Ruby's String is much alike raw C string?

It wraps a C string.
Or is there any particular optimization for String copy?

Certain operations are COW.
 

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,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top