Blocks 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
endInstead 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 helloHere 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, whilereturning 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
endThis 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 recommendedNote: 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 codeBe 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 codeyield 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)
#=> 4While 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
# 8Blocks 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
endThis 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}
endInstead 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 # => 6Or more concisely:
triple(2).callCurrying 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
endHowever, 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.