Ruby - #tap that!
Today I wanted to talk about #tap
, an addition to Ruby in versions >= 1.9, and how I’ve been using and seeing it used lately.
What is #tap
?
The feature is coded like so:
VALUE rb_obj_tap(VALUE obj)
{
rb_yield(obj);
return obj;
}
So, in Ruby:
class Object
def tap
yield self
self
end
end
What is #tap
for?
So that looks pretty simple, it just allows you do do something with an object inside of a block, and always have that block return the object itself. #tap
was created for tapping into method chains, so code like this:
def something
result = operation
do_something_with result
result # return
end
can be turned into (without modifying the contract of do_something_with
):
def something
operation.tap do |op|
do_something_with op
end
end
Where has it gone wrong?
Some early blog posts talking about tap focused on its use for inserting calls to puts
into code without modifying behavior:
arr.reverse # BEFORE
arr.tap { |a| puts a }.reverse # AFTER
This isn’t a great use, not only because it involves debugging your code solely by means of output (instead of a debugger
), but also because #tap
is so much cooler an idea than just a mechanism for inserting temporary code.
Why I like it:
In additional to the tapping behavior described in the first section, here are some other uses I’m seeing / using:
Assigning a property to an object
Especially useful when assigning a single attribute
# TRADITIONAL
object = SomeClass.new
object.key = 'value'
object
# TAPPED
object = SomeClass.new.tap do |obj|
obj.key = 'value'
end
# CONDENSED
obj = SomeClass.new.tap { |obj| obj.key = 'value' }
Ignoring method return
Useful when wanting to call a single method on an object and keep working with the object afterwards:
# TRADITIONAL
object = Model.new
object.save!
object
# TAPPED
object = Model.new.tap do |model|
model.save!
end
# CONDENSED
object = Model.new.tap(&:save!)
Using in-place operations chained
A lot of times, we expand logic in order to use in-place methods like reverse!
, but look:
# TRADITIONAL
arr = [1, 2, 3]
arr.reverse!
arr
# TAPPED CONDENSED
[1, 2, 3].tap(&:reverse!)
Conclusion:
Just like anything else, don’t overuse #tap
just for kicks. Its tempting to tap everything, but it definitely can make code less readable if used inappropriately. That being said, its a great addition to a Rubyist’s sugar toolkit. Give it a shot!