Do It, Whenever

by Adam Pearson
on 2009.12.31

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?

» permalink