Blocks (and lambdas)

Blocks and lamdbas are discrete pieces of code that aren't attached to an object or method.

Yield

To run a block use the keyword yield. This runs the implicit block defined within the curly braces {}. This implicit block is anonomous and can't be assigned to a variable

Convert an implicit block to an explicit block using the & operator. This converts it to a Proc object which has the send method to execute the block and a name assigned to it to pass around

def execute_block(number)
    # run the block and pass the number through
    yield number
end

# & converts the block into a proc
def execute_block_argument(number, &block)
    # procs can be stored in variables
    block_store = block
    # uses the call method to run the proc
    block_store.call number
end

The number argument is passed in the normal way in brackets (2) the block is passed by adding it after the method execute_block(number) {|number| number + 1}

require 'examples/block_yield'

describe 'lib/examples/block_yield' do
  it 'should yield a block' do
      expect(execute_block(2) { |number| number + 1 }).to eq 3
  end

  it 'should call a proc passed as argument' do
    expect(execute_block_argument(2) { |number| number + 1 }).to eq 3
  end

  it 'should throw error if no block is present' do
    expect { execute_block_argument(2) }.to raise_error(NoMethodError)
  end

end

What if a block isn't defined by accident? Ruby has a way to guard against this with the special method block_given? which checks if a block has been passed.

def block_check(number)
    yield if block_given?
end

If the method call doesn't include the block the action doesn't take place.

require 'examples/block_check'
describe 'examples/block_check' do
    it 'should check for block before processing' do
        expect(block_check(1)).to eq nil
    end
end

Block local variables

Rubyism:

In this example the block executed in the middle of the method increment_by_one defines the variable current_number this overwrites the variable defined in the scope also called current number which is probably not as intended.

def execute_block(number)
    yield number
end

def increment_by_one(number)
    current_number = 7
    new_number = execute_block(number) { |number|
        current_number = number + 1
        current_number
    }
    "current number: #{current_number} new number: #{new_number} original number #{number}"
    # current number: 2!               new number: 2             original number 1
end

To fix this use a block local variable. Define using ; then the name of the variable.

new_number = execute_block(number) { |number; current_number|
    # ...
}

block variable example

$ ruby examples/block-local-variables.rb
current number: 2 new number: 2 original number 1
current number: 7 new number: 2 original number 1

Lambdas

Lambdas are pretty much the same as procs with some key differences:

Prefer Lambdas over Procs as they are less error prone. The following example shows how a break can have unintended side effects.


def lambda_method
    output = 0
    # Assigns the value 2 to variable output
    lambda { output = 1 + 1; return}.call
    # Returns the value 2
    output

end

def proc_method
    output = 0
    # proc returns out of method
    proc { output = 1 + 1; return}.call
    # does not run method returns nil
    output

end

Using Proc in a case statement

Use a proc within a case statement to inject some logic into the statement beyond the simple string equals.

def block_case(number, &block)
    case number
    # calls the block with the value passed into the case
    # same as calling block.call(number)
    when block then "block equals"
    end
end

If the block returns true for a condition the then statement will be ran.

require 'examples/block_case'
describe 'examples/block_case' do
  it 'should pass block to case' do
      expect(block_case(2) { |number| number == 2 }).to eq "block equals"
  end
end

Converting a symbol to a proc

A Symbol converts to a proc with the & operator. This enables a concise map. Suppose you want to round up an array of numbers. One solution would be to use a map and pass a block which calls the ceil function on each value. To achieve the same result use the :ceil symbol, convert to a proc and pass that argument to the map method.

def symbol_to_proc?
    [1.5,2.5].map { |number| number.ceil } == [1.5,2.5].map(&:ceil)
end

TL;DR

Books

Pragmatic Ruby - Containers, Blocks, and Iterators

Exercises

Ruby Monk - Blocks chapter

Videos

Ruby Fundamentals - Blocks

Ruby Fundamentals - Block Local Variables

Challenge 🎠

Use Rspec to run the challenges

bin/rspec spec/challenges/blocks_spec.rb --format doc

fix ../lib/challenges/challenges/blocks.rb so the tests pass ✔️

That's it!

return back to the home