#returning and #tap

D

dblack

Hi --

Good point! I thought of "pipe" but even that doesn't say that it
modifies and returns self. Maybe "apply": string.split(/\s/).apply
{|i| i.pop}.whatever. Or even "modify".


'tap' had a different intent, though - "tee off the object without
disturbing it" - even if it did the same thing in the end. So you
would typically take a.foo.bar.baz.quux... and drop in a tap,
a.foo.bar.tap {|i| puts "hi mom! this is #{i}"}.baz.quux, and take
care not to modify i destructively.

Although... there might be cases where disturbing the object would be
desireable. For example, you could use it to work around the fact
that a lot of bang methods return nil when there's no change:

str = "abcde"
a = str.tap {|s| s.gsub!(/z/,"x") }.split(//)


David

--
David A. Black | (e-mail address removed)
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
 
D

dblack

S

spooq

I don't think dup'ing plays well with the intent, which is to do a
kind of pass-through of the object itself.

The original object -is- getting passed through... the point is to
prevent modification of it. That's what map! and each are for.

class Object
def tap
yield self.dup
self
end
end

class NilClass
def tap
yield nil
nil
end
end

class Fixnum
def tap
yield self
self
end
end

class Symbol
def tap
yield self
self
end
end

["abc", nil, 1, :foo].each { |e|
e.tap { |p|
puts p
}
}


Any other corner cases need fixing?
 
D

dblack

Hi --

The original object -is- getting passed through... the point is to
prevent modification of it. That's what map! and each are for.

I see what you mean. Well, as per my gsub! example, I think modifying
it can be useful :)


David

--
David A. Black | (e-mail address removed)
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
 
S

spooq

Hi --



I see what you mean. Well, as per my gsub! example, I think modifying
it can be useful :)

This can be rewritten using existing keywords tho... if you want to
actually use the original, use each and gsub!, or if you want to use
the output, then use map and gsub. Doing both at the same time just
means that your nice single chain of operations will either

a) end up giving you the same end result as your input is now modified
to be, which you have to admit is not very useful

or

b) somewhere else along the chain you'll stop modifying one, which is
going to be hideously confusing.

If the original and the output share steps, make that obvious.
 
T

Trans

Joel said:
Yes. Not much better than #tap, is it? Any ideas...? "and_then"?

well, #with is the other common synonym. but #returning seems as well.
you just read it with the object of returning being stated first, which
we sometimes do in english.

def foo
@foo ||= Foo.new.returning do |f|
f.bar = "bar"
end
end

T.
 
T

Trans

spooq said:
Actually, how about giving the proc a copy of the object, rather than
the real deal?

class Object
def tap
yield self.dup
self
end
end

"Do as if frozen". Perhaps #freeze could take a block?

T.
 
D

dblack

Hi --

This can be rewritten using existing keywords tho... if you want to
actually use the original, use each and gsub!, or if you want to use
the output, then use map and gsub. Doing both at the same time just
means that your nice single chain of operations will either

a) end up giving you the same end result as your input is now modified
to be, which you have to admit is not very useful

or

b) somewhere else along the chain you'll stop modifying one, which is
going to be hideously confusing.

If the original and the output share steps, make that obvious.

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.


David

--
David A. Black | (e-mail address removed)
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
 
D

dblack

Hi --


I rather like it too :) I guess if too many of them got chained it
could get cumbersome. But it does allow one to avoid stopping and
starting the chaining based on the nil returns.


David

--
David A. Black | (e-mail address removed)
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
 
S

spooq

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

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.
 
S

spooq

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

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.

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
 
D

dblack

Hi --


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.

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 | (e-mail address removed)
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
 
S

spooq

foo.gsub!.bar

because of the danger of nil from gsub!. That was the impetus for my
idea of using tap in this situation.

Sorry, this was a really bad example from me... I meant the equivalent
using tap notation - foo.tap { gsub! }.bar. There was no way for you
to guess that.
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.

Maybe you are more confident of your ability to handle complexity than
I am of my ability :)
You'd want a final ! there to make them equivalent :)

Are you sure? Let's look at the original again.

foo.tap { gsub! }.gsub.tap { gsub! }

foo.tap { gsub! } -> returns a modified foo
foo.gsub -> returns the result of
foo.gsub, which IS NOT FOO
returned_value.tap { gsub! } -> modifies the not-foo and returns it

Somewhere along that path, you have gone from passing foo to passing
an anonymous not-foo, and it's not poke-you-in-the-eye obvious where
that was. If you now expect foo to be returned by this chain (as you
might think when you see the first tap), you will be surprised. If you
expect foo to be modified by the last gsub, you will be surprised. If
you expect chains to leave the original alone (like all the inherently
chainable functions in Ruby do at the moment), you will be surprised.
Thats far too many surprises for my liking.

So how can we rewrite... the anonymous not-foo is only useful as a
return value, seeing as it has no name and all. Let's make that
explicit.

foo.gsub!
puts foo, somefunc(foo.gsub.gsub)

Almost by definition, if you're using a bang-function, you need foo
later on, otherwise you would just do

puts somefunc(foo.gsub.gsub.gsub)

and be done with it.

Conclusion : tap-allowing-side-effects and the equivalent,
bang-functions returning self, add nothing except headaches!
I'll just add that in my own little testbeds, I've renamed tap to
"punt" :)

punt seems like an equally valid name.
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.

We probably are at that stage, but the journey was definitely
worthwhile, so thanks :)
 
D

dblack

Hi --

We probably are at that stage, but the journey was definitely
worthwhile, so thanks :)

I'll just add that I now see exactly what you mean about mixing the
two techniques. And definitely -- one would want to be very careful
and selective. One variation on the theme might be cases where
in-place operations saved some memory, even if you end up throwing the
object away; so the dual fact that the end result was a different
object, *and* one of the intermediate objects had in-place stuff done
to it, might be OK.


David

--
David A. Black | (e-mail address removed)
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
 

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,054
Latest member
TrimKetoBoost

Latest Threads

Top