Understanding Ruby blocks, Procs and methods is not easy for a Ruby beginner, especially if Procs and Lambdas are involved. Yet the basic elements are simple, as Matz says, blocks are basically nameless functions. You can pass a nameless function to another function, and then that function can invoke the passed-in nameless function.

An ampersand in a function is a good hint for such a process. Ruby on Rails functions often have arguments which start with stars (*args) or ampersands (&block). In C programming, the * marks a pointer to a variable, while & is used to get the address of a variable. In Ruby, the splat operator * turns a list of arguments in an array, and the ampersand & marks a reference to a block which is passed to a method.

It is important to understand both methods if you want to write advanced Ruby functions using blocks, for example to use Ruby blocks for custom tags. The following analyzer example takes a list of arguments and a code block, and logs the effect of the code in the block for each argument:

def analyzer(*args, &block)
 args.each { |arg| puts "#{arg} is "+block.call(arg) }
end

>> analyzer(1,2,3,4,5) { |i| (i % 2 == 0) ? "even" : "odd" }
=> 1 is odd
=> 2 is even
=> 3 is odd
=> 4 is even
=> 5 is odd

If we define

  array = (1..5).to_a
  number_type = lambda { |i| (i % 2 == 0) ? "even" : "odd" }

then the following lines of code are equivalent and result in the output above:

  analyzer(1,2,3,4,5) { |i| (i % 2 == 0) ? "even" : "odd" }
  analyzer(1,2,3,4,5, &number_type)
  analyzer(*array, &number_type)

Blocks in Ruby are often functions which are passed to variables (contrary to parameters in methods, which are variables passed to functions). As we can see, the uses of & and * as a prefix are different for function definitions and calls: in definitions they have a capturing effect, but in calls their effect is expanding:

  • in a function definition, for example def analyzer(*args, &block),
    & captures any passed block into that object
    * captures any arguments into an array
  • in a function call, for example analyzer(*array, &number_type)
    & expands the given callback object into a block
    * expands the given array into a list of arguments

Therefore if we want to pass a proc or lambda object instead of a block to a function, we have to expand it with &:

>> sum = lambda { |x,y| x+y }
=> #(Proc:..)

>> array = (1..5).to_a
=> [1,2,3,4,5]

>> array.inject &sum
=> 15

By the way the following lines of code are equivalent, too. The shortest way to sum an array is obviously the last one:

[1, 2, 3, 4, 5].inject { |x,y| x + y  }
(1..5).inject{ |x,y| x+y }
(1..5).inject(&:+)
Advertisements