NOTE:This blog had a good run, but is now in retirement.
If you enjoy the content here, please support Gregory's ongoing work on the Practicing Ruby journal.

Code Blocks: Ruby's Swiss Army Knife

2009-07-07 04:00, written by Gregory Brown

The following blog post is a direct excerpt from the Ruby Best Practices book. If you’ve been enjoying this blog, you’d probably love the book, so I’ve decided to release some content here to give you a sense of what to expect. Enjoy!

UPDATE: For those coming from other languages, Ruby’s code blocks are inherently closures , and provide syntactic sugar for methods that accept Proc objects (Ruby’s anonymous functions). While not strictly necessary for understanding this article, a solid grasp on what closures are and how they work will take you far.

In Ruby, code blocks are everywhere. If you’ve ever used Enumerable, you’ve worked with blocks. But what are they? Are they simply iterators, working to abstract away our need for the for loop? They certainly do a good job of that:

  >> ["Blocks","are","really","neat"].map { |e| e.upcase } 
  => ["BLOCKS", "ARE", "REALLY", "NEAT"]

But other blocks don’t really iterate over collections—they just do helpful things for us. For example, they allow us to write something like:

  File.open("foo.txt","w") { |f| f << "This is sexy" } 

instead of forcing us to write this:

  file = File.open("foo.txt","w") 
  file << "This is tedious" 
  file.close 

So blocks are useful for iteration, and also useful for injecting some code between pre-processing and post-processing operations in methods. But is that all they’re good for? Sticking with Ruby built-ins, we find that isn’t the case. Blocks can also shift our scope temporarily, giving us easier access to places we want to be:

 "This is a string".instance_eval do 
    "O hai, can has reverse? #{reverse}. kthxbye" 
  end 
  #=> "O hai, can has reverse? gnirts a si sihT. kthxbye" 

But blocks aren’t necessarily limited to code that gets run right away and then disappears. They can also form templates for what to do down the line, springing to action when called for:

  >> foo = Hash.new { |h,k| h[k] = [] } 
  => {} 
  >> foo[:bar] 
  => [] 
  >> foo[:bar] << 1 << 2 << 3 
  => [1, 2, 3] 
  >> foo[:baz] 
  => [] 

So even if we label all methods that accept a block as iterators, we know the story runs deeper than that. With this in mind, we can leverage some basic techniques to utilize any of the approaches shown here, as well as some more advanced tricks. By doing things in a way that is consistent with Ruby itself, we can make life easier for our users. Rather than piling on new concepts, we can allow them to reuse their previous knowledge. Let’s take a look at a few examples of how to do that now.

Working with Enumerable

The most common use of blocks in Ruby might be the most trivial. The following class implements a basic sorted list, and then mixes in the Enumerable module. The block magic happens in each():

  class SortedList 
    include Enumerable 
  
    def initialize 
      @data = [] 
    end 
  
    def <<(element) 
      (@data << element).sort! 
    end 
  
    def each 
      @data.each { |e| yield(e) } 
    end 
  end

Our each() method simply walks over each element in our @data array and passes it through the block provided to the method by calling yield. The resulting iterator works exactly the same as Array#each and Hash#each and all the Ruby built-ins, and indeed simply wraps Array#each in this case:

  >> a = SortedList.new 
  => #<SortedList:0x5f0e74 @data=[]> 
  >> a << 4 
  => [4] 
  >> a << 5 
  => [4, 5] 
  >> a << 1 
  => [1, 4, 5] 
  >> a << 7 
  => [1, 4, 5, 7] 
  >> a << 3 
  => [1, 3, 4, 5, 7] 
  >> x = 0 
  => 0 
  >> a.each { |e| x += e } 
  => [1, 3, 4, 5, 7] 
  >> x 
  => 20

This shouldn’t be surprising. What is really the interesting bit is that by including the module Enumerable, we gain access to most of the other features we’re used to working with when processing Ruby’s built-in collections. Here are just a few examples:

  >> a.map { |e| "Element #{e}" } 
  => ["Element 1", "Element 3", "Element 4", "Element 5", "Element 7"] 
  >> a.inject(0) { |s,e| s + e } 
  => 20 
  >> a.to_a 
  => [1, 3, 4, 5, 7] 
  >> a.select { |e| e > 3 } 
  => [4, 5, 7] 

In a lot of cases, the features provided by Enumerable will be more than enough for traversing your data. However, it’s often useful to add additional features that build on top of the the Enumerable methods. We can show this by adding a simple reporting method to SortedList:

  class SortedList 
    def report(head) 
      header = "#{head}\n#{'-'*head.length}" 
      body = map{|e| yield(e)}.join("\n") + "\n" 
      footer = "This report was generated at #{Time.now}\n" 
      [header, body, footer].join("\n") 
    end 
  end

which, when run, produces output like this:

>> puts a.report("So many fish") { |e| "#{e} fish" } 
  So many fish 
  ------------ 
  1 fish 
  3 fish 
  4 fish 
  5 fish 
  7 fish 
  This report was generated at 2008-07-22 22:47:20 -0400 

Building custom iterators is really that simple. This provides a great deal of flexibility, given that the code block can execute arbitrary expressions and do manipulations of its own as it walks across the elements. But as mentioned before, blocks can be used for more than just iteration.

Using Blocks to Abstract Pre- and Postprocessing

We looked at the block form of File.open() as an example of how blocks can provide an elegant way to avoid repeating tedious setup and teardown steps. However, files are surely not the only resources that need to be properly managed. Network I/O via sockets is another place where this technique can come in handy.

On the client side, we’d like to be able to create a method that allows us to send a message to a server, return its response, then cleanly close the connection. The first thing that comes to mind is something simple like this:

  require "socket" 

  class Client 

    def initialize(ip="127.0.0.1",port=3333) 
      @ip, @port = ip, port 
    end 

    def send_message(msg) 
      socket = TCPSocket.new(@ip,@port) 
      socket.puts(msg) 
      response = socket.gets 
    ensure 
      socket.close 
    end 
  
  end

This is reasonably straightforward, but what happens when we want to add another method that waits to receive a message back from the server?

  require "socket" 
  
  class Client 
    
    def initialize(ip="127.0.0.1",port=3333) 
      @ip, @port = ip, port 
    end 
    
    def send_message(msg) 
      socket = TCPSocket.new(@ip,@port) 
      socket.puts(msg) 
      response = socket.gets 
    ensure 
      socket.close 
    end 
    
    def receive_message 
      socket = TCPSocket.new(@ip,@port) 
      response = socket.gets 
    ensure 
      socket.close 
    end 
  end

This is starting to look messy, as we have repeated most of the code between send_message and receive_message. Ordinarily, we’d break off the shared code into a private method that the two could share, but the trouble here is that the difference between these two methods is in the middle, not in a single extractable chunk. This is where blocks come to the rescue:

  require "socket" 

  class Client 
    def initialize(ip="127.0.0.1",port=3333) 
      @ip, @port = ip, port 
    end 
  
    def send_message(msg) 
      connection do |socket| 
        socket.puts(msg) 
        socket.gets 
      end 
    end 

    def receive_message 
      connection { |socket| socket.gets } 
    end
     
    private 
    
    def connection 
      socket = TCPSocket.new(@ip,@port) 
      yield(socket) 
    ensure 
      socket.close 
    end 
  
  end

As you can see, the resulting code is a lot cleaner. As long as we use our connection() method with a block, we won’t need to worry about opening and closing the TCPSocket; it’ll handle that for us. This means we’ve captured that logic in one place, and can reuse it however we’d like.

To make things a bit more interesting, let’s take a look at a simple server with which this code can interact, which gives us a chance to look at yet another way that blocks can be useful in interface design.

Blocks as Dynamic Callbacks

There is a lot of power in being able to pass around code blocks just like they were any other object. This allows for the capability of creating and storing dynamic callbacks, which can later be looked up and executed as needed.

In order to play with our Client code from the previous example, we’re going to create a trivial TCPServer that attempts to match incoming messages against patterns to determine how it should respond. Rather than hardcoding behaviors into the server itself or relying on inheritance to handle responses, we will instead allow responses to be defined through ordinary method calls accompanied by a block. Our goal is to get an interface that looks like this:

  server = Server.new 

  server.handle(/hello/i) { "Hello from server at #{Time.now}" } 
  server.handle(/goodbye/i) { "Goodbye from server at #{Time.now}" } 
  server.handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}!" } 

  server.run

The first two examples are fairly simple, matching a single word and then responding with a generic message and timestamp. The third example is a bit more interesting, repeating the client’s name back in the response message. This will be accomplished by querying a simple MatchData object, which is yielded to the block.

Though making this work might seem like black magic to the uninitiated, a look at its implementation reveals that it is actually a fairly pedestrian task:

  class Server 
    
    def initialize(port=3333) 
      @server   = TCPServer.new('127.0.0.1',port) 
      @handlers = {} 
    end 
    
    def handle(pattern, &block) 
      @handlers[pattern] = block 
    end 
    
    def run 
      while session = @server.accept 
        msg = session.gets 
        match = nil 
        @handlers.each do |pattern,block| 
          if match = msg.match(pattern) 
            break session.puts(block.call(match)) 
          end 
        end 
        unless match 
          session.puts "Server received unknown message: #{msg}" 
        end 
      end 
    end
     
  end

The handle() method slurps up the provided block using the &block syntax, and stores it in a hash keyed by the given pattern. When Server#run is called, an endless loop is started that waits for and handles client connections. Each time a message is received, the hash of handlers is iterated over. If a pattern is found that matches the message, the associated block is called, providing the match data object so that the callback can respond accordingly.

If you’d like to try this out, use the following code to spin up a server:

  server = Server.new 
  server.handle(/hello/i) { "Hello from server at #{Time.now}" } 
  server.handle(/goodbye/i) { "Goodbye from server at #{Time.now}" } 
  server.handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}!" } 
  server.run

Once you have that running and listening for connections, execute the following client code:

  client = Client.new 
  
  ["Hello", "My name is Greg", "Goodbye"].each do |msg| 
    response = client.send_message(msg) 
    puts response 
  end 

You will get back something like this:

  Hello from server at Wed Jul 23 16:15:37 -0400 2008 
  Nice to meet you Greg! 
  Goodbye from server at Wed Jul 23 16:15:37 -0400 2008 

It would be easy to extend both the client and server to do more interesting things that build on this very simple foundation. Feel free to take a few minutes to play around with that, and then we’ll look at one more block trick that’s fairly common in Ruby.

Blocks for Interface Simplification

Does it feel like the word “server” is written too many times in this code?

  server = Server.new 
  server.handle(/hello/i) { "Hello from server at #{Time.now}" } 
  server.handle(/goodbye/i) { "Goodbye from server at #{Time.now}" } 
  server.handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}!" } 
  server.run 

When you see code like this, it might be a sign that you could do better. Although there are merits to this somewhat standard approach, we can cheat a little bit with blocks (of course) and make things prettier. It would be nice to be able to write this instead:

  Server.run do 
    handle(/hello/i) { "Hello from server at #{Time.now}" } 
    handle(/goodbye/i) { "Goodbye from server at #{Time.now}" } 
    handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}!" } 
  end 

As you may recall from an earlier example, it is possible to execute a block within the scope of an instantiated object in Ruby. Using this knowledge, we can implement this handy shortcut interface as a simple class method:

  class Server 
    # other methods same as before 
    def self.run(port=3333,&block) 
      server = Server.new(port) 
      server.instance_eval(&block) 
      server.run 
    end 
  end

This is all you need to get the new interface running, and rounds off our quick exploration of the different ways that you can use blocks to improve your API design while simplifying your method implementations. Let’s recap with a few tips before we wrap up here.

Things to Remember

Keep the following things in mind when using blocks as part of your interface:

  • If you create a collection class that you need to traverse, build on top of Enumerable rather than reinventing the wheel.
  • If you have shared code that differs only in the middle, create a helper method that yields a block in between the pre/postprocessing code to avoid duplication of effort.
  • If you use the &block syntax, you can capture the code block provided to a method inside a variable. You can then store this and use it later, which is very useful for creating dynamic callbacks.
  • Using a combination of &block and instance_eval, you can execute blocks within the context of arbitrary objects, which opens up a lot of doors for highly customized interfaces.
  • The return value of yield (and block.call) is the same as the return value of the provided block.

Did you enjoy this post?

If you had fun reading this, definitely consider picking up a copy of Ruby Best Practices . If you need more convincing, there is a whole sample chapter for you to check out in addition to this excerpt. If you’ve already picked up the book and are enjoying it, tell your friends, and consider leaving a review on the Amazon product page . If you don’t have enough time for that, go help me win the popularity contest over at RubyTrends, as there are several books that I admire that still need to be overcome :)

As always, I welcome your thoughts and feedback. Let me know what you think of the techniques shown here, or share your own personal favorite code block trick.

blog comments powered by Disqus