Hi --
> 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
···
On Mon, 20 Nov 2006, spooq wrote:
On 11/20/06, spooq <spoooq@gmail.com> wrote:
On 11/17/06, dblack@wobblini.net <dblack@wobblini.net> wrote:
--
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] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org