This is a Big Picture™ post. I will attempt to give you a thorough understanding of how blocks and procs works in Ruby by showing examples and explaining basics. I will also show some examples from other languages, namely Javascript and Lua, in order to really expand on the Big Picture™ in this article.
Storing pieces of code
In Ruby, you can store pieces of code and execute them on demand; methods. You define the method, add code to the method body, and run the method whenever you want.
In addition to methods, there is another way in Ruby to store code that you can call later; procs.
say_hello = Proc.new { puts "Hello!" }
Contrary to what you might expect, the puts
doesn’t run. Ruby only stores the code inside the proc, it doesn’t run it. Not yet, at least. It runs when we tell it to. Let’s run it right away. Remember, we stored the proc in the say_hello
variabe.
say_hello.call
# => "Hello!"
So, there you go. That’s what procs are, and that is all there is to it. But how the fsck is this useful to anyone? Why not just use methods?
Organizing your code with procs
Procs are useful code organization tools. Let’s say that you want to calculate the running time for a few code snippets, in order to benchmark them. To do this elegantly, you want to be able to call a method and pass some code to it, and have the execution time returned.
Here’s how you can use procs to do just that:
def time(a_proc)
start = Time.now
a_proc.call
puts Time.now - start
end
time(Proc.new { code_here })
time(Proc.new { more_code_here })
The execution times of the code inside the procs will be neatly displayed as you run this code. Try it yourself, right now! You can use sleep(2)
, 1000.times { 5 + 5 }
, or anything else you might want to speed test.
Blocks!
In addition to method arguments, a method can also take a block. Passing a block is essentially the same as passing a proc, except that the syntax is different (and nicer), and you don’t have to type as much. Other than that, procs and blocks are essential the same thing: code that you can call later on.
def time
start = Time.now
yield
puts Time.now - start
end
time { code_here }
time { more_code_here }
Looks a lot nicer, doesn’t it? Upon further inspection, it is easy to see where procs and blocks differs. Comparing the two examples, we can see that the time
method no longer takes an argument. A block is a special internal thing in Ruby, so you don’t need to specify a method argument for the block. Also, we use yield
instead of a_proc.call
. yield
is a special keyword in Ruby, telling Ruby to call the code in the block that was passed to that method. Lastly, actually passing the code to the method has a different syntax. No parentheses, because the block is not an argument. No Proc.new
either, because we aren’t creating a proc, we are passing a block.
Passing arguments to procs and blocks
When you yield
or call
, you can pass arguments to the proc or block at the same time. A dumb example:
def dumb_hello_world_test
yield(5)
end
dumb_hello_world_test {|i| puts i * 2 }
# => 10
my_silly_proc = Proc.new {|name| puts name.upcase }
my_silly_proc.call("August Lilleaas")
# => "AUGUST LILLEAAS"
You have probably seen blocks taking arguments before: each
, map
and other enumerable methods does this.
[1, 2, 3].map {|i| puts i * 2 }
# => 2
# => 4
# => 6
That’s right: map takes a block. The block gets an argument: the number in the array. Now, for something hardcore. Let’s play with an implementation of Array#each
.
class Array
def each
i = 0
while(i < self.length) do
yield(self[i])
i += 1
end
end
end
my_array = ["a", "b", "c"]
my_array.each {|letter| puts letter }
# => "a"
# => "b"
# => "c"
We iterate the items in the array, and call the block — yield — for every item in the array. When we yield, we pass the current array iteration to the block.
The big picture: blocks and procs in other languages.
Ruby isn’t the only language that lets you pass around chunks of code. Here are some examples in Javascript and Lua. Despite their obvious use case differences, these two languages are pretty similar.
Remember how you can use both methods and procs/blocks to store chunks of code in Ruby? In JS/Lua, methods and procs/blocks are replaced by functions. This is possible because, unlike Ruby, you can refer to a function by name without calling it, or create anonymous functions.
// javascript
var sayHello = function(){
alert("Hello, World!");
}
sayHello // a reference
sayHello() // calling it
-- lua
local function sayHello()
print("Hello, World!");
end
sayHello -- a reference
sayHello() -- calling it
Again, unlike Ruby, sayHello
won’t execute the function, while sayHello()
will. Let’s create an implementation of the time
method in JS and Lua.
// javascript
var time = function(callback){
var start = new Date();
callback();
alert(new Date.getTime() - start.getTime());
}
time(function(){
// run some code here..
});
-- lua
local function time(callback)
local start = os.time();
callback();
print(os.time() - start);
end
time(function()
-- run some code here..
end);
As you can see, you can pass an anonymous function definition directly to the two time
functions, as an argument — similar to the way you Ruby lets you pass procs as method arguments.
Just to elaborate, and as mentioned, you can pass a reference to a function as well as an actual function definition.
time(sayHello)
And just to make sure you’re with me: time(sayHello())
will break miserably. sayHello
will execute before time
, which causes time
to use whatever executing sayHello
returns, instead of the function reference.
Lastly, a Ruby recap. In Ruby, foo
and foo()
are equivalents.
def say_hello
puts "Hello, World!"
end
say_hello
# => "Hello, World!"
say_hello()
# => "Hello, World!"
So, while other languages are able to pass around chunks of code by using references to other functions, Ruby doesn’t allow that. Ruby has a separate feature for this, though; procs and blocks.
Relevant boring facts: Blocks to procs to blocks
You can convert a block passed to a method into a proc by using special syntax for this.
def time(&block)
puts block
end
time
# => nil
time { foo }
# => #<Proc:0x00029bbc>
You can also do this the other way around: pass a proc to a method and make it look as if you passed a block.
def time
yield
end
my_proc = Proc.new { puts "I was called!" }
time(&my_proc)
# => "I was called!"
The ampersand is the key here, which tells Ruby that block
and my_proc
isn’t a method argument but a reference to the block passed to that method. Leave out the ampersand, and it’ll be treated as a regular method argument.
def time(block)
puts block
end
time { foo }
# => ArgumentError: wrong number of arguments (0 for 1)
# Because the ampersand was left out in the method definition, the method now expects an
# argument, and it is given none (a block is not a method argument).
def time
yield
end
time(my_proc)
# => ArgumentError: wrong number of arguments (1 for 0)
# Because the time method doesn't take any arguments, and without the ampersand, my_proc
# is passed as a regular method argument.
Block syntax
do ... end
and { ... }
are equivalents. These two calls to time
are identical.
time { code_here }
time do
code_here
end
The ideom is to use do ... end
on multi-line blocks, and { ... }
for one-liner blocks. It is entirely up to you, though. Following the ideom is recommended, but Ruby doesn’t enforce it.
A note on lambdas
Short answer: lambda { foo }
and proc { foo }
is essensially the same thing, you can use both interchangeable in your code.
There is two differences, though: One is how it handles return
. I suggest reading this article.
The other is how it handles arguments. This is really lame and strange. lambda
and proc
are equivalents, while Proc.new
differs. You’d think Proc.new
and proc
were equivalents, but they aren’t. I’ll let this code speak for itself.
a_lambda = lambda {|a| a.inspect }
puts a_lambda.call
# => nil
puts a_lambda.call("foo", 5)
# => ["foo", 5]
sumlam = lambda {|a, b| a + b }
sumlam.call
# => ArgumentError: wrong number of arguments (0 for 2)
sumproc = proc {|a, b| a + b }
sumproc.call
# => ArgumentError: wrong number of arguments (0 for 2)
sumproc.call(4, 5)
# => 9
orlyproc = Proc.new {|a, b| a + b }
orlyproc.call
# => NoMethodError: undefined method `+' for nil:NilClass
orlyproc.call(4, 5)
# => 9