Monday, 21 March 2011

Ruby: block

Blocks can be closures
In computer science, a closure is a first-class function with free variables that are bound in the lexical environment. Blocks are closures, which means variables in the surrounding scope that are referenced in a block remain accessible for the life of that block and the life of and Proc object created from that block.

Example:
def n_times(thing)
  lambda {|n| thing * n}
end

p1 = n_times(23)
p1.call(3) # => 69
p1.call(4) # => 92

Compare with Python closures:
def generate_power_func(n):
  def nth_power(x): 
    return x**n
  return nth_power
end

raised_to_4 = generate_power_func(4)
raised_to_4(2)
Blocks can be objects
Block can be converted to an object of class Proc. There are several ways where blocks are converted to objects.
  • If the last parameter in a method definition is prefixed with an ampersand (such as &action), Ruby looks for a code block whenever that method is called. 
  • Use lambda or its alternative -> form. For example:
    bo = lambda { |param| puts "You called me with #{param}" }
    bo.call 99
    bo.call "cat"
    # produces:
    # You called me with 99
    # You called me with cat
    
    lam = ->(p1, p2) { p1 + p2 }
    lam.call(4, 3) # => 7
    Compare this with the "bound function operator" in CoffeeScript:
    callback = (message) => @voicement.push message
    
  • Use Proc.new

  • The call method on a proc object invokes the code in the original block.

The Symbol.to_proc trick
Ruby implements the to_proc for objects of class symbol.
names = %w{ant bee cat}
result = names.map {|name| name.upcase}
result = names.map {&:upcase}
The last line means: apply the upcase method to each element of names. This works by relying on Ruby's type coercion. When you say names.map(&xxx), you're telling Ruby to pass the Proc object in xxx to the map method as a block. If xxx isn't already a Proc object, Ruby tries to coerce it into one by sending it a to_proc message. If it was written Ruby, it would look something like this:
def to_proc
  proc { |obj, *args| obj.send(self, *args) } # same as lambda
end
It's an incredibly elegant use of coercion and of closures. However, the use of dynamic method invocations mean that the version of code that uses &:upcase is about half as fast as the more explicitly coded block. This doesn't matter so much unless in the performance-critical section of your code.

No comments :

Post a Comment