Skip to content

Instantly share code, notes, and snippets.

@bracken
Created August 25, 2009 23:21
Show Gist options
  • Select an option

  • Save bracken/175116 to your computer and use it in GitHub Desktop.

Select an option

Save bracken/175116 to your computer and use it in GitHub Desktop.
A presentation on ruby blocks
<html>
<head>
<link href="style/shCore.css" rel="stylesheet" type="text/css"/>
<link href="style/shThemeDefault.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="style/shCore.js"></script>
<script type="text/javascript" src="style/shBrushRuby.js"></script>
<style type="text/css">
pre {
margin-left: 15px;
background-color: whitesmoke;
}
hr {
margin-bottom:400px;
}
li{
line-height:150%;
}
span{
font-family: monospace;
font-size: large;
}
</style>
<script language="javascript">
SyntaxHighlighter.all();
SyntaxHighlighter.defaults['gutter'] = false;
</script>
</head>
<body>
<h1>Ruby Essentials: Blocks and Yield</h1>
<li>Gist of the presentation html: <a href="http://gist.github.com/175116">http://gist.github.com/175116</a></li>
<li>Follow along: <a href="http://codespeicher.com/urug/yield/">http://codespeicher.com/urug/yield/</a></li>
<h2>What is a block?</h2>
<li>Simply code between braces or in a do/end.</li>
<pre class="brush: ruby">
{ puts "Hello" }
do
puts "Howdy"
end
</pre>
<li>Blocks can only be associated with a call to a method. (Proc/lambda offer further functionality)</li>
<li>Braces are generally used for one-liners, do/end for multi-line blocks.</li>
<hr />
<h2>We use them all the time</h2>
<li>Repeat a command multiple times</li>
<pre class="brush: ruby">
2.times {puts "hip"}
3.times do
puts "hurray"
end
</pre>
<li>Iterate over the items of an array (Blocks can take arguments)</li>
<pre class="brush: ruby">
[1,2,3,4].each {|i| puts i }
[5,6,7,8].each do |i|
puts i
end
</pre>
<li>Map an array</li>
<pre class="brush: ruby">
%w(ruby block yield).map {|e| "#{e} rocks"}
#==> ["ruby rocks", "block rocks", "yield rocks"]
</pre>
<li>Writing to a file</li>
<pre class="brush: ruby">
File.open("temp.txt", 'w') do |f|
f << "blocks are nice\n"
end
</pre>
<li>Using instance_eval to get in a different scope.</li>
<pre class="brush: ruby">
"forward".instance_eval do
"This is me backwards: #{reverse}."
end
# ==> "This is me backwards: drawrof."
</pre>
<hr />
<h2>Using blocks in your code</h2>
<li>Ruby keyword: yield</li>
<pre class="brush: ruby">
def block_caller
puts "in the method, yielding to block:"
yield
yield
puts "back in the method"
end
block_caller {puts "Hi from Block"}
</pre>
<li>Passing values to a block</li>
<pre class="brush: ruby">
def block_caller
puts "in the method, yielding to block with an arg:"
yield "first yield..."
yield "second yield..."
puts "back in the method"
end
block_caller {|val| puts "Hi from Block for #{val}"}
</pre>
<li>Create named block and 'call' it</li>
<li>The '&' is the key here</li>
<pre class="brush: ruby">
def block_namer( &block )
puts "In method"
block.call
puts "Back in method"
end
block_namer { puts "In block" }
</pre>
<li>Blocks must be the last arguments to the method.</li>
<li>You can use "block_given?" to test whether a block was passed</li>
<pre class="brush: ruby">
def block_checker(arg1, &block)
puts "block_given = #{block_given?}, block = #{block.inspect}"
end
block_checker("an arg") {}
block_checker "an arg"
</pre>
<hr />
<h2>Blocks are closures!</h2>
<li>Blocks retain the scope from where they're created.</li>
<pre class="brush: ruby">
my_name = "Rubert"
2.times {puts "hey #{my_name}"}
</pre>
<li>Even if the scope is gone</li>
<pre class="brush: ruby">
class BlockSaver
def save_block( &block )
@block = block
end
def call_block
@block.call
end
end
def set_block(saver)
temp = "My scope is gone!"
saver.save_block { puts "Is it gone? nope: #{temp}" }
end
#note that the closure will reflect the latest changes to a variable in its scope
def set_block_alt(saver)
temp = "My scope is gone!"
saver.save_block { puts "Is it gone? nope: #{temp}" }
temp = "I changed the variable"
end
saver = BlockSaver.new
set_block(saver)
saver.call_block
# ==> Is it gone? nope: My scope is gone!
set_block_alt(saver)
saver.call_block
# ==> Is it gone? nope: I changed the variable
</pre>
<hr />
<h2>Why use blocks?</h2>
<li>Blocks can be used to clean up the interface of your objects.</li>
<li>If you must do the same setup/tear down each time blocks may be a good solution.</li>
<li>The ruby File library is a good example:</li>
<pre class="brush: ruby">
#not so elegant solution:
file = File.open("sad.txt", 'w')
file << "Old-school write"
file.close
</pre>
<li>Because the setup/cleanup are each one line this might not be a big deal, but it's not too hard to forget to close your file.</li>
<li>With a block you don't have to remember to call close</li>
<pre class="brush: ruby">
File.open("happy.txt", 'w') { |f| f << "New-school?" }
</pre>
<hr />
<h2>Interface enhancement example</h2>
<li>This is from the Ruby Best Practices Book</li>
<li>This will illustrate how to implement blocks as an interface</li>
<pre class="brush: ruby">
#Change this
pdf = Document.new
pdf.text "I'm a pdf, I'm cool"
pdf.render_file "hi.pdf"
pdf.close
#Into this
Document.generate("hi.pdf") do
text "I'm an even cooler pdf"
end
#The class:
class Document
def text(val)
puts val
end
def render_file(file)
puts "Rendered file: #{file}"
end
def self.generate(file, *args, &block)
pdf = Document.new(*args)
pdf.instance_eval(&block)
pdf.render_file(file)
pdf.close
end
end
</pre>
<li>That's great, but there is a downside.</li>
<li>What will happen when this is used:</li>
<pre class="brush: ruby">
class FriendHolder
def initialize
@first = "John"
@last = "Doe"
end
def full_name
"#{@first} #{@last}"
end
def generate_pdf
Document.generate("friends.pdf") do
text "My friend is #{full_name}"
end
end
end
f = FriendHolder.new
f.generate_pdf
</pre>
<li>instance_eval changes the scope to the object it's called on.</li>
<li>The Document class has no <em>full_name</em> method. What if it did?</li>
<li>Is there a way to fix this problem, but still allow the same interface?</li>
<li>Introducing, block.arity</li>
<pre class="brush: ruby">
class Document
def text(val)
puts val
end
def render_file(file)
puts "Rendered file: #{file}"
end
def self.generate(file, *args, &block)
pdf = Document.new(*args)
block.arity &lt; 1 ? pdf.instance_eval(&block) : block.call(pdf)
pdf.render_file(file)
pdf.close
end
end
#allows both this
Document.generate("hi.pdf") do
text "I'm an even cooler pdf"
end
#and this
Document.generate("hi.pdf") do |pdf|
pdf.text "I'm an even cooler pdf"
end
</pre>
<li>Now what must the friend holder look like?</li>
<pre class="brush: ruby">
class FriendHolder
def initialize
@first = "John"
@last = "Doe"
end
def full_name
"#{@first} #{@last}"
end
def generate_pdf
Document.generate("friends.pdf") do |pdf|
pdf.text "My friend is #{full_name}"
end
end
end
f = FriendHolder.new
f.generate_pdf
</pre>
<hr />
<h2>Ruby 1.9 changes</h2>
<li>Block arguments area always local-only scope</li>
<pre class="brush: ruby">
something = "Big Bird"
[1,2].each {|something| puts something}
something
# ruby1.8 ==> 2
# ruby1.9 ==> "Big Bird"
</pre>
<li>You can list variables you want to be local-only with a ';' in the args</li>
<pre class="brush: ruby">
something_else = "Grouch"
[1,2].each {|something; something_else| something_else = something}
something_else
# ruby1.8 ==> syntax error, unexpected ';', expecting '|'
# ruby1.9 ==> "Grouch"
</pre>
<li>Other Ruby 1.9 changes:</li>
<ul>
<li>You can specify default values</li>
<li>You can specify splat (starred) arguments</li>
<li>You can pass a block to the block. (use '&' for last argument)</li>
</ul>
<hr />
<h2>Things to keep in mind when using blocks</h2>
<li>You can use <span>Enumerable</span> to help build a collection class without reinventing the wheel</li>
<li>For code that only differs in the middle, yielding to a block prevents duplication of effort</li>
<li>Using the <span>&block</span> syntax, you can capture a block and use it as a dynamic callback</li>
<li>Using <span>&block/instance_eval</span> you can execute code in the context of arbitrary objects</li>
<li>The return value of <span>yield</span> and <span>block.call</span> are the same as the return value of the provided block</li>
<li>Using the <span>return</span> keyword in a block returns from the scope it was called in. (lambda works differently)</li>
<li>For further exploration you can study Procs and lambda</li>
<h2>The End</h2>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment