Created
August 25, 2009 23:21
-
-
Save bracken/175116 to your computer and use it in GitHub Desktop.
A presentation on ruby blocks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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 < 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