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(&:+)