Sonntag, 22. Juni 2014

Block it! ... a Ruby closure

A closure is a anonymous function together with referencing environment and it has access to those non-local variables even when invoked outside.
The Ruby block is one of the Ruby ways of implementing a closure. And it is the most trivial.
Its domain is:
  1. Preventing code repetition by extracting its unique part.
  2. Moving logic into objects (it is about responsibility).
  3. Hiding internal object design by restricting its API to be more qualified.
The original code example:
class Stack
  attr_reader :store
  def initialize *elements
    @store = elements
  end
end
is a simple stack class, offering a reader method, which returns a collection of objects. It can be used like:
stack = Stack.new 'Ruby', 1, { language: 'Ruby' }
stack.store.map { |element| 
  element.to_s + ": " + element.object_id.to_s
}
=> Ruby: 14531380
   1: 3
   {:language=>"Ruby"}: 14531340
stack.store.map { |element|
  element.to_s + ": " + element.public_methods.join(', ')
}
=> Ruby: <=>, ==, ===, eql?, ...
   1: to_s, inspect, -@, +, ...
   {:language=>"Ruby"}: rehash, to_hash, to_h, to_a, ...
The code duplication is obvious. Not tot mention the exposure of the internal storage. Furthermore the public reader method can be more specific about its intention:
class Stack
  def initialize *elements
    @store = elements
  end

  def stringify
    @store.map { |element|
      element.to_s + ": " + yield(element).to_s
    }
  end
end
The reader method has been removed and the new stringify method offers a stringification of the stack object. The key word yield is a placeholder for the block, where it is processed. The iterating element is assigned to yield as the parameter (the number of parameters for yield in general can vary from zero to whatever).
The stringifying messages to the stack can be sent like:
stack = Stack.new 'Ruby', 1, { language: 'Ruby' }
stack.stringify { |element| element.object_id }
=> Ruby: 13656480
   1: 3
   {:language=>"Ruby"}: 13656440
stack.stringify { |element| element.public_methods.join(', ') }
=> Ruby: <=>, ==, ===, eql?, ...
   1: to_s, inspect, -@, +, ...
   {:language=>"Ruby"}: rehash, to_hash, to_h, to_a, ...
The messages results are analog to the original example, but the refactored code is more intentional and DRY.

Supported by Ruby 1.9.3