Block
A block is code inside do
and end
or {
and }
. It’s not an object.
A block alone is a syntax error, it can only exist as a part of method calls.
{ puts "lol" }.call
# BAD! Syntax error! Block is not a proc!
{ puts "lol" }.to_proc.call
# BAD! Syntax error! Block is not even an object!
A method call can have zero or one block after its regular arguments. Every method can take a block, even if it doesn’t make sense because the method doesn’t use block.
puts "lol" do
puts "this block has no sense, but it will not throw any error"
end
# the result is:
# lol
# => nil
Block is just a special syntax for a piece of code just for passing that piece of code as an argument of a method.
Blocks are made to inject code (in anonymous and literally form) into methods to make them more versatile.
def universal_but_useless_method
yield
end
universal_but_useless_method do
puts "I am a block"
end
# the result is:
# I am a block
# => nil
def print_block_result_in_a_fancy_way(a, b)
result = yield
puts "The result of doing something with #{a} and #{b} is... #{result}!!!!!!"
end
a = 2
b = 7
print_block_result_in_a_fancy_way(a, b) do
a + b
end
# the result is:
# The result of doing something with 2 and 7 is... 9!!!!!!
# => nil
print_block_result_in_a_fancy_way(a, b) do
a * b
end
# the result is:
# The result of doing something with 2 and 7 is... 14!!!!!!
# => nil
There are not such things as “named blocks”.
def foo(&block)
word = "Ho"
block.call(word)
block.call()
p block
end
foo do | name |
puts "Hey! #{name}!"
end
# it prints:
# Hey! Ho!
# Hey! !
# #<Proc:0x007f702ab4a608@main.rb:11>
block
is an instance of Proc.
This method definition
def foo(&block)
block.call("John")
end
is the same as
def foo
block = Proc.new
block.call("John")
end
Proc
Procs exist in Ruby because Ruby doesn’t have functions and properties. Ruby has only methods and Ruby allows you to skip parenthesis during the method call.
But functions are useful in many languages if they are “first-class citizens”. So in Ruby procs (instances of Proc class) are objects with just a #call
method with a few aliases. They can be called and passed as argument and returned like every objects.
def print_proc_result_in_a_fancy_way(a, b, operation)
result = operation.call(a, b)
puts "The result of doing something with #{a} and #{b} is... #{result}!!!!!!"
end
adding = Proc.new { |a, b| a + b }
multiplying = Proc.new { |a, b| a * b }
print_proc_result_in_a_fancy_way(3, 5, adding)
# the result is:
# The result of doing something with 3 and 5 is... 8!!!!!!
# => nil
print_proc_result_in_a_fancy_way(3, 5, multiplying)
# the result is:
# The result of doing something with 3 and 5 is... 15!!!!!!
# => nil
Proc doesn’t care about number of arguments. It takes argument as nil if there was nothing passed and ignores any extra arguments.
proc = Proc.new do |x|
if x
result = x.to_s
else
result = "this was nil"
end
result
end
puts proc.call
puts proc.call(1)
puts proc.call(2, 3, 1)
# it prints:
# this was nil
# 1
# 2
Proc can be invoked in many ways.
adding = Proc.new { |a, b| a + b }
puts adding.call(2, 3)
# 5
# => nil
puts adding.(2, 3)
# 5
# => nil
puts adding[2, 3]
# 5
# => nil
puts adding.yield(2, 3)
# 5
# => nil
Proc can be created in many ways too.
adding = Proc.new { |a, b| a + b }
adding = proc { |a, b| a + b }
But procs can’t be created without any block!
This code:
blockless_proc = Proc.new
blockless_proc.call
will raise an exception.
Unless there is some block passed to the current scope.
That’s why this code will work;
def foo
block = Proc.new
block.call
end
foo do
puts "this block will be passed automatically"
end
Lambda
Lambda, unlike proc, raise ArgumentError if number of arguments is incorrect.
Creating lambda:
adding = lambda { |a, b| a + b }
adding = ->(a, b) { a + b }
Calling lambda (same as calling proc):
p adding.call(8, 5)
p adding.(3, 9)
p adding[2, 2]
p adding.yield(2, 3)
Return in proc and lambda
In lambdas return
means the same as next
.
a = [4,6,3,2,5]
l = lambda do |n|
if n % 2 == 0
return n * 2 # or next n * 2
else
return 1 # or next 1
end
end
p = proc do |n|
if n % 2 == 0
next n * 2
else
next 1
end
end
p a.map(&l) # => [8, 12, 1, 4, 1]
p a.map(&p) # => [8, 12, 1, 4, 1]
Return in non-lambda procs (and blocks) raises LocalJumpError
:
a = [4,6,3,2,5]
p = proc do |n|
if n % 2 == 0
return n * 2
else
return 1
end
end
p a.map(&p) # => unexpected return (LocalJumpError)
Here the first return terminates the whole method because the proc is defined in a method definition:
def convert_array(a)
p = proc do |n|
if n % 2 == 0
return n * 2
else
return 1
end
end
return a.map(&p)
end
p convert_array([8, 12, 1, 4, 1]) # => 16
Here it raises LocalJumpError too, because the proc definition is not in a method definition:
p = proc do |n|
if n % 2 == 0
return n * 2
else
return 1
end
end
def convert_array(a, p)
return a.map(&p)
end
p convert_array([8, 12, 1, 4, 1], p) # => unexpected return (LocalJumpError)