Passing a lambda as a block

28,412

Solution 1

Tack an ampersand (&) onto the argument, for example:

("A".."K").each &procedure

This signifies that you're passing it as the special block parameter of the method. Otherwise it's interpreted as a normal argument.

It also mirrors they way you'd capture and access the block parameter inside the method itself:

# the & here signifies that the special block parameter should be captured
# into the variable `procedure`
def some_func(foo, bar, &procedure)
  procedure.call(foo, bar)
end

some_func(2, 3) {|a, b| a * b }
=> 6

Solution 2

The trick is in using an & which tells Ruby to convert this argument to a Proc if necessary and then use the object as the method’s block. Starting from Ruby 1.9 there's a shortcut for lambda (anonymous) functions. So, you can write code like this:

(1..5).map &->(x){ x*x }
# => [1, 4, 9, 16, 25]

will take each element of the array and compute its power

it is the same as this code:

func = ->(x) { x*x }
(1..5).map &func

for Ruby 1.8:

(1..5).map &lambda {|x| x*x}
# => [1, 4, 9, 16, 25]

To solve your problem you can use Array's method reduce (0 is initial value):

('A'..'K').reduce(0) { |sum,elem| sum + elem.size }
# => 11

Passing a lambda function to reduce is a bit tricky, but the anonymous block is pretty much the same as lambda.

('A'..'K').reduce(0) { |sum, elem| ->(sum){ sum + 1}.call(sum) }
# => 11

Or you could concat letters just like this:

('A'..'K').reduce(:+)
=> "ABCDEFGHIJK"

Convert to lowercase:

('A'..'K').map &->(a){ a.downcase }
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]

In the context of a method definition, putting an ampersand in front of the last parameter indicates that a method may take a block and gives us a name to refer to this block within the method body.

Solution 3

The other answers left something un-clarified that I'd like to expand on. How do we pass an arg and a block to a method?

Suppose we have a method that takes an arg and a block:

def method_with_arg_and_block(arg)
  puts arg
  yield
end

and a proc:

pr = proc { puts 'This is a proc'}

The answer: It's important that you pass the proc in as an arg with an ampersand rather than appending the proc to the method (like you would do with a block).

For example if you do:

method_with_arg_and_block('my arg') &pr

you will get a "no block given (yield)" exception.

The correct way to call this is:

method_with_arg_and_block('my arg', &pr)

Ruby will take care of converting the proc to a block and appending it to your method. Note: since lambdas are also procs, this will work with lambdas as well.

Thanks to https://medium.com/@sihui/proc-code-block-conversion-and-ampersand-in-ruby-35cf524eef55 for helping me understand this.

Share:
28,412
heneryville
Author by

heneryville

I'm a software engineer on the Narrative and Context team at Ancestry.com. We try to tell the story of your ancestors lives. By discovering interesting context about ancestors and communicating them in compelling natural language stories, we hope to bring or relatives lives into a vivid story, rather than a collection of records and facts. SOreadytohelp

Updated on July 05, 2022

Comments

  • heneryville
    heneryville almost 2 years

    I'm trying to define a block that I'll use to pass the the each method of multiple ranges. Rather than redefining the block on each range, I'd like to create a lamba, and pass the lambda as such:

    count = 0
    procedure = lambda {|v| map[count+=1]=v}
    ("A".."K").each procedure
    ("M".."N").each procedure
    ("P".."Z").each procedure
    

    However, I get the following error:

    ArgumentError: wrong number of arguments(1 for 0)
        from code.rb:23:in `each'
    

    Any ideas what's going on here?