Eavesdropping on Expressions
Update: Slightly more complete examples.
I found a nice little technique for debugging Ruby code today.
Ever had a situation where you wanted to insert some debugging code in the middle of an expression? The usual way is to break up the expression and use intermediate variables to get at the value, but it turns out that’s really not necessary in Ruby.
Check this out:
class Object
def tap
yield self
self
end
end
Then, you can insert your debugging tap just about anywhere without disturbing the flow of data. Let’s look at some common cases.
First, let’s look a “pipeline” of sorts.
blah.sort.grep( /foo/ ).map { |x| x.blah }
Let’s imagine that there’s a bug here somewhere — we’ve verified that x.blah does the right thing, but the values coming from upstream are suspect. Here’s a “traditional” way of modifying this to add a debugging print:
xs = blah.sort.grep( /foo/ )
p xs
# do whatever we had been doing with the original expression
xs.map { |x| x.blah }
With Object#tap, this becomes much easier — you can just slip it in without radically modifying the code:
blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
Similarly, let’s say we’re suspicious of a component, ( q - t ), in an arithmetic expression:
( k + 1 ) / ( ( q - t ) / 2 )
The traditional approach:
i = ( q - t )
p i
( k + 1 ) / ( i / 2 )
Admittedly, it may be wise to break long arithmetic expressions up like this for comprehensibility anyway.
Regardless, here’s how you could do the same thing using Object#tap:
( k + 1 ) / ( ( q - t ).tap { |i| p i } / 2 )
Object#tap is also useful when you’re directly using the result of an expression as the result of a method. For example:
def blah
@things.map { |x|
x.length
}.inject( 0 ) { |a, b|
a + b
}
end
The traditional way:
def blah
sum = @things.map { |x|
x.length
}.inject( 0 ) { |a, b|
a + b
}
p sum
sum
end
The Object#tap way:
def blah
@things.map { |x|
x.length
}.inject( 0 ) { |a, b|
a + b
}.tap { |sum| p sum }
end
If you think about it, Object#tap is a bit like tee in Unix, really. Ever used tee while debugging a shell pipeline, to make sure that the intermediate results were sane? Same thing.
Anyone else discovered this trick?