From: dblack@... Date: 2006-11-20T22:42:39+09:00 Subject: Re: #returning and #tap Hi -- On Mon, 20 Nov 2006, spooq wrote: > On 11/20/06, spooq wrote: >> On 11/17/06, dblack@wobblini.net wrote: >> >> > I'm afraid I don't follow. Can you show how you'd write this in the >> > ways you've described? Since you can't reliably chain gsub! with >> > anything else, I'm not sure how it would play out. >> >> What I mean is to chain with gsub instead, or to make it obvious that >> you are changing the original by using separate lines. >> >> In effect, you want to do this... >> >> foo.gsub!.bar >> >> I'm saying, if you want functional style chaining, obey the unwritten >> rules and don't have side-effects in the chain. >> >> foo.gsub.bar You're mixing real and quasi-pseudo-code examples here, though. One would never actually do: foo.gsub!.bar because of the danger of nil from gsub!. That was the impetus for my idea of using tap in this situation. >> If you want to modify the original, use traditional imperative style >> with side-effects. >> >> foo.gsub! >> foo.bar >> >> Mixing the two styles is just going to cause confusion in long chains. >> Think of it this way... do you care about foo, or about the return >> value of foo.gsub ? By modifying in a chain, you're saying you care >> about both, even though they have the same value. Not a problem, you >> say? Then think about this case.... >> >> foo.gsub!.gsub.gsub! >> >> I feel sorry for the maintenance programmer already. The ! is there to give a heads-up for possible side-effects. Also, I think I have a different view of chaining than you do. I view chaining very strictly left-to-right. So say you've got this: foo.any_method_whatsoever Once that call is done, it's done. If this is added to it: .some_other_method that's a completely new transaction. In other words, array.sort! in this snippet: array.sort!.map {|s| s.capitalize } is no more or less imperative than array.sort! in this snippet: array.sort! array.map {|s| s.capitalize } The snag is that a lot of these in-place change methods in Ruby return nil when no change happens. Otherwise it would be perfectly fine to do: string.gsub!(...).each_byte {...} gsub! is documented as performing in-place changes, so there's no stealth or ambiguity at all -- *except* the nil issue, which is indeed a deal-breaker. Hence the tap solution. > I think I need to make this more explicit for it to be clear. Only the > gsub!'s need to be wrapped in tap()'s... the gsub must be directly > inline because of the need to use it's return value. Wrapping it is > equivalent to deleting it from the chain. My point is really about it > not being clear when foo itself is being passed along, as opposed to > something derived from applying a function to foo. Does that make more > sense? I get the feeling I'm not explaining myself very well. > > foo.tap { gsub! }.gsub.tap { gsub! } > > should be written as > > foo.gsub! > foo.gsub.gsub You'd want a final ! there to make them equivalent :-) And you'd only need one tap. In any case, as long as one knows what tap does, and what gsub! does, there's no ambiguity or unclarity with the tap version; it's all out in the open. I'll just add that in my own little testbeds, I've renamed tap to "punt" :-) The idea being: punt the object across this method call and block -- but still, of course, execute the block in full, with the object passed in. We're probably at the agree-to-disagree stage, but hopefully it's been of interest to both of us and anyone interested in the topic. David -- David A. Black | dblack@rubypal.com Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3] DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4] [1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com [2] http://dablog.rubypal.com | [4] http://www.rubycentral.org