-
Object#tap - Ruby’s K Combinator
Say we want a method that returns an instance of the class Logger. We want this method to either return a previously instantiated object, if it exists, or we want to instantiate one. In Ruby we have a nice piece of syntactic sugar known as “or equals”, previously covered here. To briefly recapitulate what it does is return the object on the lefthand side when it is not nil, or it evaluates the righthand side, assigns it to the lefthand side, and return that value.
def logger @logger ||= Logger.new(STDOUT) end
Very nice, beautiful, and simple. However what happens when you need to call a method on the resulting object? Something that can’t be passed as an argument into the constructor? Using the same example of our logger method, one might want different datetime formatting on their log entries.
def logger @logger ||= Logger.new(STDOUT) @logger.datetime_format = '%c' @logger end
Terrible. The datetime setter is being called each time the logger method itself is called. Horrible, wasteful, unnecessary, and ugly. We could get around this by putting a return statement in there, but this too is ugly and quite quickly the method becomes bloated. I believe it is better to embrace what Ruby is: a synthesis of the most powerful and flexible computer science concepts and ideas.
New in Ruby 1.9 and backported to 1.8.7 is Object#tap, Ruby’s implementation of theĀ K combinator. Tap makes some really beautiful code possible. Simply put, what it does is take the object it is called on, pass it into a block, whatever is inside the block is evaluated, and the object is returned even if the block returns something different. This also means you can get your method chaining game on nice!
Let’s see what our logger method would look like using this functional style:
def logger @logger ||= Logger.new(STDOUT).tap { |o| o.datetime_format = '%c' } endCome on dude! How beautiful is that? One line of ninjitsu, and I believe perfectly clear once you know what Object#tap is.
Conceptually, Object#tap is very simple. When called on an object, the objects yields to a block, passing itself in as a block variable and finally returns self after that. So, if you’re still on 1.8.6 and are going a little green with envy right now, don’t worry, thanks to Ruby’s open classes you can still get the good stuff.
Object.class_eval { def tap() yield self; self end }Ok, enough one liners for one day. :p
S
Fri05Mar