Ruby Blocks and Procs and Lambdas

From WikiOD

Syntax[edit | edit source]

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

Remarks[edit | edit source]

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.

Lambdas[edit | edit source]

# 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"

Partial Application and Currying[edit | edit source]

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[edit | edit source]

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[edit | edit source]

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.

Objects as block arguments to methods[edit | edit source]

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]

Converting to Proc[edit | edit source]

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"

Blocks[edit | edit source]

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[edit | edit source]

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[edit | edit source]

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.

Proc[edit | edit source]

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.

Credit:Stack_Overflow_Documentation