Blocks in Ruby

OxRug 21 August 2014

Ben Holmes

github.com/bdvholmes/blocks-in-ruby-talk

What's a block?

Passing instructions to a method

sometimes do
  puts 'hi oxrug!'
end

Accepting variables

greet_oxrug do |message|
  puts message
end

These are formally called closures

message = 'hi oxrug!'

sometimes do
  puts message
end

Scope

message = 'hi oxrug!'

sometimes do
  message = 'bye oxrug'
  puts message
end

# message = ?

Block level scope

message = 'hi oxrug!'

sometimes do |a, b, c; message|
  message = 'bye oxrug'
  puts message
end

# message = 'hi oxrug!'

Fun with splats

sometimes do |a, *b|
  # a = first argument
  # b = list of the others
end

sometimes do |*a, b|
  # a = list of all arguments except last
  # b = last argument
end

Syntax

Blocks

sometimes do
  puts 'hi oxrug!'
end

Alternatively define with braces for one-liners

sometimes { puts 'hi oxrug!' }

greet { |message| puts message }

Can only be used in method calls

Procs

greet_oxrug = Proc.new do
  puts 'hi oxrug!'
end

# or 

greet_oxrug = Proc.new { puts 'hi oxrug!' }

sometimes greet_oxrug

Alternatively

greet_oxrug = proc { puts 'hi oxrug!' }

Procs are first order objects

greet_oxrug = Proc.new do
  puts 'hi oxrug!'
end

greet_oxrug.call

Call with variables

greet_oxrug = Proc.new do |message|
  puts message
end

greet_oxrug.call('hi oxrug')

Lambdas are special Procs

greet_oxrug = lambda{ |message| puts message }

greet_oxrug.call

Alternatively

greet_oxrug = ->(message){ puts message }

Lambdas are strict with variable count

Proc.new{ |message| puts message }.call

# No complaints

lambda{ |message| puts message }.call

# ArgumentError: wrong number of arguments (0 for 1)

Proc returns stop methods

def do_proc
  Proc.new{return}.call
  puts 'still here'
end

def do_lambda
  lambda{return}.call
  puts 'still here'
end

do_proc

# silence

do_lambda

# still here

Lambda is like an anonymous method

def do_it(it)
  it.call
  puts 'still here'
end

do_it Proc.new{ return }

# LocalJumpError: unexpected return

do_it lambda{ return }

# still here

Deeper?

Methods are not first order objects

But you can get a Method with the method method

def say_hi
 puts 'hi oxrug!'
end

method(:say_hi).class
# Method

method(:say_hi).call
# Hi Oxrug

Also symbol has a to_proc constuctor

:foo.to_proc

# is like saying

Proc.new{|x| x.foo}

Magical unary & operator

Calls to_proc and turns into a block

['hi', 'oxrug'].map(&:capitalize).join

# is like

['hi', 'oxrug'].map {|message| message.capitalise }.join

So you can write stuff like

(1..3).inject(&:+)
# 6

(1..5).select(&:odd?)
# [1, 3, 5]

Handling blocks

All methods implicitly accept one block

def always
  yield
end

def maybe
  yield if rand(1..6) > 2
end

def rarely
  yield if rand(1..6) == 6
end

Passing arguments

def greet_oxrug
  yield 'hi oxrug!'
end

greet_oxrug do |message|
  puts message
end

When do I have a block?

def greet_oxrug
  if block_given?
    yield 'hi oxrug!'
  else
    puts 'hi oxrug!'
  end
end

Explicit definition

def greet_oxrug(&block)
  block.call
end

Here the & is changing the block into a proc -.-

Apparently explicit blocks are slow too...

Examples

Great with array methods

(1..5).map {|x| x ** 2}
# [1, 4, 9, 16, 25]

(1..5).reject {|x| x == 2}
# [1, 3, 4, 5]

(1..5).sort {|a, b| b <=> a}
# [5, 4, 3, 2, 1] 

As seen everywhere in rails

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
      t.timestamps
    end
  end
end
describe CsvUtility do
  it '.read_cell' do

    test = [['A1', 'B1'], ['A2', 'B2']]
    expect(CsvUtility.read_cell test, 'A', 1).to eq('A1')
    expect(CsvUtility.read_cell test, 'B', 1).to eq('B1')
    expect(CsvUtility.read_cell test, 'A', 2).to eq('A2')
    expect{CsvUtility.read_cell test, 'ยง', 1}.to raise_error

  end
end
<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body, size: "60x12" %>
  <%= f.submit "Create" %>
<% end %>

The form_for method yields a form builder object (the f variable)

@people = Person.all

respond_to do |format|
  format.html
  format.json { render json: @people.to_json }
end
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>
Book.where(:title => 'Tale of Two Cities').first_or_create do |book|
  book.author = 'Charles Dickens'
  book.published_year = 1859
en
t = Thread.new do
  People.transaction do
    # Do loads of stuff
  end
end
at_exit { t.join }
class RubyClosures
  include Enumerable

  def each
    yield "blocks"
    yield "procs"
    yield "lambdas"
  end
end

Pub

Next OxRug: 18 Sepetember?