Method Chaining Issues

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

···

On Thu, Jun 02, 2005 at 01:15:22AM +0900, aartist wrote:

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?

Hello

···

On 1 Jun 2005, at 17:15, aartist wrote:

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?

Drop the !s

string = string.sub('old','mew').reverse

Seems fine to me!

Tom

--
http://tom.counsell.org

aartist wrote:

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

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!

Phrogz wrote:

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

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

Phrogz wrote:

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

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 = (0..10000).to_a
      ary.reverse!
      ary.sort!
    end
  end
  bm.report("Non-destructive") do
    1000.times do
      ary = (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)

···

On 6/2/05, Nikolai Weibull <mailing-lists.ruby-talk@rawuncut.elitemail.org> wrote:

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

--
http://nohmad.sub-port.net

Sam Goldman wrote:

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

Definitely,
        nikolai

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

Gyoung-Yoon Noh wrote:

···

On 6/2/05, Nikolai Weibull ><mailing-lists.ruby-talk@rawuncut.elitemail.org> wrote:

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

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

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
kheon@comcast.net

Gyoung-Yoon Noh wrote:

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

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

Gyoung-Yoon Noh wrote:

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),

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

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?

       nikolai

E

···

Le 1/6/2005, "Nikolai Weibull" <mailing-lists.ruby-talk@rawuncut.elitemail.org> a écrit:

--
template<typename duck>
void quack(duck& d) { d.quack(); }

Kyle Heon wrote:

> > Some people think that "bang" methods shouldn't exist at all!

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)

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

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)

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.

···

On 6/2/05, Nikolai Weibull <mailing-lists.ruby-talk@rawuncut.elitemail.org> wrote:

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.

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),

--
http://nohmad.sub-port.net

Kyle Heon wrote:

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.

It's not a Ruby thing... "bang" is just common programmer slang
for the exclamation point.

Hal

Kyle Heon wrote:

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.

Oops, failed to address the other half of that.

The ! usually (not always) indicates that a method changes its
receiver "in place" rather than creating a new object as a result.

I gather that functional programming people dislike this -- but I
have never used a real FP language (such as Haskell?).

Hal

ES wrote:

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

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

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.

···

On 01 Jun 2005, at 18:36, Gyoung-Yoon Noh wrote:

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

Just curious, can you give me an example of a bang method that doesn't
modify the
receiver in place?

···

On 6/1/05, Hal Fulton <hal9000@hypermetrics.com> wrote:

Kyle Heon wrote:
>
> 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.
>

Oops, failed to address the other half of that.

The ! usually (not always) indicates that a method changes its
receiver "in place" rather than creating a new object as a result.