From: bbiker Date: 2007-07-24T03:49:58+09:00 Subject: Re: Is there a replacement for sub? On Jul 23, 11:51 am, bbiker wrote: > On Jul 22, 10:39 pm, "Michael W. Ryder" <_mwry...@worldnet.att.net> > wrote: > > > > > > > dbl...@wobblini.net wrote: > > > Hi -- > > > > On Sun, 22 Jul 2007, Bernard Kenik wrote: > > > >> ruby-talk-ad...@ruby-lang.org wrote: > > > >>> ------------------------------------------------------------------------ > > > >>> Subject: > > >>> Re: Is there a replacement for sub? > > >>> From: > > >>> "Robert Dober" > > >>> Date: > > >>> Fri, 20 Jul 2007 20:49:07 +0900 > > >>> To: > > >>> ruby-t...@ruby-lang.org (ruby-talk ML) > > > >>> To: > > >>> ruby-t...@ruby-lang.org (ruby-talk ML) > > > >>> On 7/20/07, Martin DeMello wrote: > > >>>> On 7/20/07, Robert Dober wrote: > > > >>>> > I was reading this whole thread and I kind of think to be dreaming, I > > >>>> > must have missed something obvious!!! > > >>>> > Anyway maybe this is was OP wants, well I think it is ;) > > > >>>> > 529/29 > irb > > >>>> > irb(main):001:0> a='a b c d e f' > > >>>> > => "a b c d e f" > > >>>> > irb(main):002:0> a.gsub!(" ","") > > > >>>> Nope, he wants a method that only replaces the first n occurrences of > > >>>> the pattern. gsub will not do this - you need to run sub in a loop. > > >>>> (This is where perl's "continue matching where you left off" would > > >>>> have been a nice optimisation) > > > >> The primary problem is that sub! returns nil when no substitutions are > > >> made. > > > >> I have similar problem with Array#flatten!, Array#uniq! since it > > >> causes problem when chaining! > > >> My solution has been to override these bang! methods to return self > > >> even when the bang! method did not have to change the receiver. > > > >> for sub! I would override the sub! method as follow > > > >> class String > > >> alias old_sub! sub! > > >> def sub!(pattern, replacement) > > >> self.old_sub!(pattern, replacement) > > >> self > > >> end > > >> end > > > >> As a user of a method, I really do not care if the method did not have > > >> to do anything so long as the object is in the desired state. > > > >> For an array such as arr = [1,2,3,4,5,6], I can safely chain the > > >> bang! methods without worrying about nil returns from flatten! and uniq! > > > >> arr.flatten!.uniq!.sort! # NO ERROR MSG "undefined method `uniq!' > > >> for nil:NilClass (NoMethodError)" because flatten! would have returned > > >> nil > > >> p arr => [1, 2, 3, 4, 5, 6] > > > > I would very, very strongly advise you, and everyone else, not to do > > > this. You will break any code (inside the standard library and/or any > > > other code you load, or any code that uses your code) that depends on > > > the documented behavior of these methods. You may not like how sub! > > > and friends work, but it's a very bad idea to make the decision to > > > change them on behalf of everyone else, over and above the language > > > documentation. > > > What I planned to do was get rid of the alias and change the name of the > > new sub! method to something like subf! for just that reason. I still > > prefer this version much better than the original version as there are > > no surprises. It is very hard to change 30+ years of practice overnight. > > > > David- Hide quoted text - > > > - Show quoted text -- Hide quoted text - > > > - Show quoted text - > > Actually you do not have to define subf! as an intermediate step > you can directly define subn and subn! directly > > class String > def subn!(pattern, replacement, n = 1) > n.times { self.sub!(pattern, replacement) } > self # or return self > end > > def subn(pattern, replacement, n = 1) > return self if n < 1 > @str = self.sub(pattern, replacement) > (n-1).times { @str = @str.sub(pattern, replacement) } > @str # or return @str > end > end > > Note that sub and sub! are the original definitions > > You use subn(pattern, replacement) and subn!(pattern, replacement) as > substitutes for sub(pattern, replacement) and sub!(pattern, > replacement) which eliminate the unpleasant surprises. > > In addition, both can be safely chained !!!! > > Hope this helps- Hide quoted text - > > - Show quoted text - You might consider adding the following, so you have a matching replacement set for sub, sub!, gsub, and gsub! You can think of gsubn and gsubn! as no nil returns :>) class String def subn!(pattern, replacement, n = 1) n.times { self.sub!(pattern, replacement) } self end def subn(pattern, replacement, n = 1) return self if n < 1 @str = self.sub(pattern, replacement) (n-1).times { @str = @str.sub(pattern, replacement) } @str end def gsubn!(pattern, replacement) self.gsub!(pattern, replacement) self # does not return nil if no changes were made end alias gsubn gsub # you can use either gsubn or gsub end My primary objection to have a nil return is that it prevents me from safely chaining bang! methods. The nil return is counter-intuitive and violates the Principle of Least Surprise As I said before given an array such as arr = [1, 2, 3, 4, 5, 6], I can do new_arr = arr.flatten.uniq.sort => [1, 2, 3, 4, 5, 6] Intuitively I would think that I should be able to do arr.flatten!.uniq!.sort!; however because of the nil return by #flatten!, a NoMethodError is raised by #uniq! since the nil object does not have a uniq! method. Note that not all bang! methods return nil when nothing was changed in the receiver. Array#sort! return self if self was already sorted. Hopefully Matz might be reading this thread and might consider changing the behavior of bang! methods returns.