Do It, Whenever
In this post I narrate a spontaneous code kata I recently explored. I was reading some online samples of ruby code and saw something like the following:
# some counter
@counter = 0
def something
some_operation()
@counter += 1
if(@counter >= 100)
do_some_periodic_operation()
@counter = 0
end
end
I have seen this idiom countless times in my experience. For some reason it looked odd to me today. I thought to myself, “with ruby, can’t this be done easily with closures?”
So I opened up my text editor and tried it out. My first attempt being the following method:
#! /usr/bin/env ruby
def when_limit_reached(counter, limit, &block)
counter += 1
if counter >= limit
yield if block_given?
counter = 0
end
counter
end
# Test
@index = 0
6.times do |i|
puts "#{i}"
@index = when_limit_reached(@index, 2) do
puts "limit met"
end
end
Running the test, I saw what I wanted to see:
$ ./attempt1.rb
0
1
limit met
2
3
limit met
4
5
limit met
$
What bugged me about this is the redundant reference to @index:
@index = incrementally(@index, 2) do ... end
Since, in ruby, method parameters are scoped within method defintions, @index has to be supplied to the method, and set on return in order for this concept to work. It seemed like there could be a better way.
So I created attempt2.rb, and tried this out with a simple class:
#! /usr/bin/env ruby
class LimitReachingDoer
def initialize(index = 0, limit = 10)
@index = index
@limit = limit
end
def when_limit_reached &block
@index += 1
if @index >= @limit
yield @index
@index = 0
end
end
end
# Test
@doer = LimitReachingDoer.new(0, 2)
6.times do |i|
puts "#{i}"
@doer.when_limit_reached do
puts "limit met "
end
end
So this is pretty cool. You just create a simple small class with a limit, and a starting index, call #when_limit_reached on every iteration, and when your limit is reached, it runs the block.
But what if I wanted my index to decrement, and fire my operation off when the index hits 0? Or what if I wanted to do something every time the index is a power of 3? Wouldn’t it be cool if I could pass in a predicate lambda to determine when to run my block?
#! /usr/bin/env ruby
class PredicateMatchingDoer
def initialize(index = 0, &reset_index)
@reset_index = reset_index
@index = index
end
def when(predicate, &block)
@index += 1
if predicate.call(@index)
yield if block_given?
@index = 0
@index = @reset_index.call(@index)
end
end
end
# notice that the reset lambda is specified as the argument to #new
@doer = PredicateMatchingDoer.new {|index| i = 21}
@matcher = lambda{|i| i % 3 == 0}
9.times do |i|
puts i
@doer.when(@matcher) do
puts "limit matched"
end
end
The above class that does just this, including a block that defines how the index should be reset after every event. This is a bit of overkill, but I thought why not go a little overboard in the last example.
With this PredicateMatchingDoer, the original code from the top of this post could be re-written as:
def something
some_operation()
@doer.when(@match) do
do_some_periodic_operation()
end
end
What is nice about this is that the usual cruft, (creating a counter, incrementing it, checking it, resetting it), is all for the most part either encapsulated into the doer class, or explicitly set elsewhere. This leaves just the important meaty parts left in the method, thereby clarifying the intent of the code.
While I may not opt for the full-featured predicate-matching doer as a standby tool in everyday coding, it certainly was an interesting exercise and highlights the power and flexibility of ruby in helping to clarify intent and encapsulate redundancy.
Next up, EventBasedDoer?
