Horje

Ruby - Blocks and Procs and Lambdas
Start
  Blocks and Procs and Lambdas
  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 (opens new window):

$ 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 (opens new window).

  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, Symbol (opens new window)s 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 (opens new window) objects:

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

  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"

  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"


Read Article
https://develop.horje.com/learn/1434/reference