Code Blocks and Iteration
(Page 1 of 4 )
Code blocks can be very confusing to newcomers to Ruby, despite the fact that many computer languages have something that functions in a similar manner. This article, the first of three parts, introduces you to code blocks. It is excerpted from chapter eight of the
Ruby Cookbook, written by Lucas Carlson and Leonard Richardson (O'Reilly, 2006; ISBN: 0596523696). Copyright © 2006 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.
In Ruby, a code block (or just “block”) is an object that contains some Ruby code, and the context neccesary to execute it. Code blocks are the most visually distinctive aspect of Ruby, and also one of the most confusing to newcomers from other languages. Essentially, a Ruby code block is a method that has no name.
Most other languages have something like a Ruby code block: C’s function pointers, C++’s function objects, Python’s lambdas and list comprehensions, Perl’s anonymous functions, Java’s anonymous inner classes. These features live mostly in the corners of those languages, shunned by novice programmers. Ruby can’t be written without code blocks. Of the major languages, only Lisp is more block-oriented.
Unlike most other languages, Ruby makes code blocks easy to create and imposes few restrictions on them. In every other chapter of this book, you’ll see blocks passed into methods like it’s no big deal (which it isn’t):
[1,2,3].each { |i| puts i}
# 1
# 2
# 3
In this chapter, we’ll show you how to write that kind of method, the kinds of method that are useful to write that way, and when and how to treat blocks as first-class objects.
Ruby provides two syntaxes for creating code blocks. When the entire block will fit on one line, it’s most readable when enclosed in curly braces:
[1,2,3].each { |i| puts i }
# 1
# 2
# 3
When the block is longer than one line, it’s more readable to begin it with thedokeyword and end it with theendkeyword:
[1,2,3].each do |i|
if i % 2 == 0
puts "#{i} is even."
else
puts "#{i} is odd."
end
end
# 1 is odd.
# 2 is even.
# 3 is odd.
Some people use the bracket syntax when they’re interested in the return value of the block, and thedo...endsyntax when they’re interested in the block’s side effects.
Keep in mind that the bracket syntax has a higher precedence than thedo..endsyntax. Consider the following two snippets of code:
1.upto 3 do |x|
putsx
end
# 1
# 2
# 3
1.upto 3 { |x| puts x }
# SyntaxError: compile error
In the second example, the code block binds to the number 3, not to the function call1.upto 3. A standalone variable can’t take a code block, so you get a compile error. When in doubt, use parentheses.
1.upto(3) { |x| puts x }
# 1
# 2
# 3
Usually the code blocks passed into methods are anonymous objects, created on the spot. But you can instantiate a code block as aProcobject by callinglambda. See Recipe 7.1 for more details.
hello = lambda { "Hello"}
hello.call
# => "Hello"
log = lambda { |str| puts "[LOG] #{str}" }
log.call("A test log message.")
# [LOG] A test log message.
Like any method, a block can accept arguments. A block’s arguments are defined in a comma-separated list at the beginning of the block, enclosed in pipe characters:
{1=>2, 2=>4}.each { |k,v| puts "Key #{k}, value #{v}" }
# Key 1, value 2
# Key 2, value 4
Arguments to blocks look almost like arguments to methods, but there are a few restrictions: you can’t set default values for block arguments, you can’t expand hashes or arrays inline, and a block cannot itself take a block argument.*
SinceProcobjects are created like other objects, you can create factory methods whose return values are customized pieces of executable Ruby code. Here’s a simple factory method for code blocks that do multiplication:
def times_n(n)
lambda { |x| x * n }
end
The following code uses the factory to create and use two customized methods:
times_ten = times_n(10)
times_ten.call(5) # => 50
times_ten.call(1.25) # => 12.5
circumference = times_n(2*Math::PI)
circumference.call(10) #
=> 62.8318530717959
circumference.call(3) #
=> 18.8495559215388
[1, 2, 3].collect(&circumference)
# => [6.28318530717959, 12.5663706143592, 18.8495559215388]
You may have heard people talking about Ruby’s “closures.” What is a closure, and how is it different from a block? In Ruby, there is no difference between closures and blocks. Every Ruby block is also a closure.†
So what makes a Ruby block a closure? Basically, a Ruby block carries around the context in which it was defined. A block can reference the variables that were in scope when it was defined, even if those variables later go out of scope. Here’s a simple example; see Recipe 7.4 for more.
ceiling = 50
# Which of these numbers are less than the target?
[1, 10, 49, 50.1, 200].select { |x| x < ceiling }
# => [1, 10, 49]
The variableceilingis within scope when the block is defined, but it goes out of scope when the flow of execution enters theselectmethod. Nonetheless, the block can accessceilingfrom withinselect, because it carries its context around with it. That’s what makes it a closure.
We suspect that a lot of people who say “closures” when talking about Ruby blocks just do it to sound smart. Since we’ve already ruined any chance we might have had at sounding smart, we’ve decided refer to Ruby closures as just plain “blocks” throughout this book. The only exceptions are in the rare places where we must discuss the context that makes Ruby’s code blocks real closures, rather than “dumb” blocks.
Next: 7.1 Creating and Invoking a Block >>
More Ruby-on-Rails Articles
More By O'Reilly Media
|
This article is excerpted from chapter seven of the Ruby Cookbook, written by Lucas Carlson and Leonard Richardson (O'Reilly, 2006; ISBN: 0596523696). Check it out today at your favorite bookstore. Buy this book now.
|
|