Ruby LanguageBlocks and Procs and Lambdas

Syntax

  • Proc.new(block)
  • lambda { |args| code }
  • ->(arg1, arg2) { code }
  • object.to_proc
  • { |single_arg| code }
  • do |arg, (key, value)| code end

Remarks

Be careful about operator precedence when you have a line with multiple methods chained, like:

str = "abcdefg"
puts str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end

Instead of printing something like abCDeFg, like you'd expect, it prints something like #<Enumerator:0x00000000af42b28> -- this is because do ... end has lower precedence than methods, which means that gsub only sees the /./ argument, and not the block argument. It returns an enumerator. The block ends up passed to puts, which ignores it and just displays the result of gsub(/./).

To fix this, either wrap the gsub call in parentheses or use { ... } instead.

Proc

def call_the_block(&calling); calling.call; end

its_a = proc do |*args|
  puts "It's a..." unless args.empty?
  "beautiful day"
end

puts its_a       #=> "beautiful day"
puts its_a.call  #=> "beautiful day"
puts its_a[1, 2] #=> "It's a..." "beautiful day"

We've copied the method call_the_block from the last example. Here, you can see that a proc is made by calling the proc method with a block. You can also see that blocks, like methods, have implicit returns, which means that procs (and lambdas) do too. In the definition of its_a, you can see that blocks can take splat arguments as well as normal ones; they're also capable of taking default arguments, but I couldn't think of a way to work that in. Lastly, you can see that it's possible to use multiple syntaxes to call a method -- either the call method, or the [] operator.

Lambdas

# lambda using the arrow syntax
hello_world = -> { 'Hello World!' }
hello_world[]
# 'Hello World!'

# lambda using the arrow syntax accepting 1 argument
hello_world = ->(name) { "Hello #{name}!" }
hello_world['Sven']
# "Hello Sven!"

the_thing = lambda do |magic, ohai, dere|
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
end

the_thing.call(1, 2, 3)
# magic! 1
# ohai 3
# 2 means hello

the_thing.call(1, 2)
# ArgumentError: wrong number of arguments (2 for 3)

the_thing[1, 2, 3, 4]
# ArgumentError: wrong number of arguments (4 for 3)

You can also use -> to create and .() to call lambda

the_thing = ->(magic, ohai, dere) {
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
}

the_thing.(1, 2, 3)
# => magic! 1
# => ohai 3
# => 2 means hello

Here you can see that a lambda is almost the same as a proc. However, there are several caveats:

  • The arity of a lambda's arguments are enforced; passing the wrong number of arguments to a lambda, will raise an ArgumentError. They can still have default parameters, splat parameters, etc.

  • returning from within a lambda returns from the lambda, while returning from a proc returns out of the enclosing scope:

    def try_proc
      x = Proc.new {
        return # Return from try_proc
      }
      x.call
      puts "After x.call" # this line is never reached
    end
    
    def try_lambda
      y = -> {
        return # return from y
      }
      y.call
      puts "After y.call" # this line is not skipped
    end
    
    try_proc # No output
    try_lambda # Outputs "After y.call"
    

Objects as block arguments to methods

Putting a & (ampersand) in front of an argument will pass it as the method's block. Objects will be converted to a Proc using the to_proc method.

class Greeter
  def to_proc
    Proc.new do |item|
      puts "Hello, #{item}"
    end
  end
end

greet = Greeter.new

%w(world life).each(&greet)

This is a common pattern in Ruby and many standard classes provide it.

For example, Symbols implement to_proc by sending themselves to the argument:

# Example implementation
class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

This enables the useful &:symbol idiom, commonly used with Enumerable objects:

letter_counts = %w(just some words).map(&:length)  # [4, 4, 5]

Blocks

Blocks are chunks of code enclosed between braces {} (usually for single-line blocks) or do..end (used for multi-line blocks).

5.times { puts "Hello world" } # recommended style for single line blocks

5.times do
    print "Hello "
    puts "world"
end   # recommended style for multi-line blocks

5.times {
    print "hello "
    puts "world" } # does not throw an error but is not recommended

Note: braces have higher precedence than do..end

Yielding

Blocks can be used inside methods and functions using the word yield:

def block_caller
    puts "some code"
    yield
    puts "other code"
end
block_caller { puts "My own block" } # the block is passed as an argument to the method.
#some code
#My own block
#other code

Be careful though if yield is called without a block it will raise a LocalJumpError. For this purpose ruby provides another method called block_given? this allows you to check if a block was passed before calling yield

def block_caller
  puts "some code" 
  if block_given? 
    yield
  else
    puts "default"
  end
  puts "other code"
end
block_caller 
# some code
# default
# other code
block_caller { puts "not defaulted"}
# some code
# not defaulted
# other code

yield can offer arguments to the block as well

def yield_n(n)
  p = yield n if block_given?
  p || n 
end
yield_n(12) {|n| n + 7 } 
#=> 19 
yield_n(4) 
#=> 4

While this is a simple example yielding can be very useful for allowing direct access to instance variables or evaluations inside the context of another object. For Example:

class Application
  def configuration
    @configuration ||= Configuration.new
    block_given? ? yield(@configuration) : @configuration
  end
end
class Configuration; end

app = Application.new 
app.configuration do |config| 
  puts config.class.name
end
# Configuration
#=> nil 
app.configuration
#=> #<Configuration:0x2bf1d30>

As you can see using yield in this manner makes the code more readable than continually calling app.configuration.#method_name. Instead you can perform all the configuration inside the block keeping the code contained.

Variables

Variables for blocks are local to the block (similar to the variables of functions), they die when the block is executed.

my_variable = 8
3.times do |x|
    my_variable = x 
    puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8

Blocks can't be saved, they die once executed. In order to save blocks you need to use procs and lambdas.

Converting to Proc

Objects that respond to to_proc can be converted to procs with the & operator (which will also allow them to be passed as blocks).

The class Symbol defines #to_proc so it tries to call the corresponding method on the object it receives as parameter.

p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]

Method objects also define #to_proc.

output = method( :p )

[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"

Partial Application and Currying

Technically, Ruby doesn't have functions, but methods. However, a Ruby method behaves almost identically to functions in other language:

def double(n)
  n * 2
end

This normal method/function takes a parameter n, doubles it and returns the value. Now let's define a higher order function (or method):

def triple(n)
  lambda {3 * n}
end

Instead of returning a number, triple returns a method. You can test it using the Interactive Ruby Shell:

$ irb --simple-prompt
>> def double(n)
>>   n * 2
>> end
=> :double
>> def triple(n)
>>   lambda {3 * n}
>> end
=> :triple
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x007fd07f07bdc0@(irb):7 (lambda)>

If you want to actually get the tripled number, you need to call (or "reduce") the lambda:

triple_two = triple(2)
triple_two.call # => 6

Or more concisely:

triple(2).call

Currying and Partial Applications

This is not useful in terms of defining very basic functionality, but it is useful if you want to have methods/functions that are not instantly called or reduced. For example, let's say you want to define methods that add a number by a specific number (for example add_one(2) = 3). If you had to define a ton of these you could do:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

However, you could also do this:

add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)

Using lambda calculus we can say that add is (λa.(λb.(a+b))). Currying is a way of partially applying add. So add.curry.(1), is (λa.(λb.(a+b)))(1) which can be reduced to (λb.(1+b)). Partial application means that we passed one argument to add but left the other argument to be supplied later. The output is a specialized method.

More useful examples of currying

Let's say we have really big general formula, that if we specify certain arguments to it, we can get specific formulae from it. Consider this formula:

f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)

This formula is made for working in three dimensions, but let's say we only want this formula with regards to y and z. Let's also say that to ignore x, we want to set it's value to pi/2. Let's first make the general formula:

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}

Now, let's use currying to get our yz formula:

f_yz = f.curry.(Math::PI/2)

Then to call the lambda stored in f_yz:

f_xy.call(some_value_x, some_value_y)

This is pretty simple, but let's say we want to get the formula for xz. How can we set y to Math::PI/2 if it's not the last argument? Well, it's a bit more complicated:

f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}

In this case, we need to provide placeholders for the parameter we aren't pre-filling. For consistency we could write f_xy like this:

f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}

Here's how the lambda calculus works for f_yz:

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_yz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (π/2) # Reduce => 
f_yz = (λy.(λz.(sin((π/2)*y) * sin(y*z) * sin(z*(π/2))))

Now let's look at f_xz

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_xz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (λt.t) (π/2)  # Reduce =>
f_xz = (λt.(λz.(sin(t*(π/2)) * sin((π/2)*z) * sin(z*t))))

For more reading about lambda calculus try this.