<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Object Mentor Blog: Tag matching</title>
    <link>http://blog.objectmentor.com/articles/tag/matching</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description></description>
    <item>
      <title>Tighter Ruby Methods with Functional-style Pattern Matching, Using the Case Gem</title>
      <description>&lt;p&gt;Ruby doesn&amp;#8217;t have overloaded methods, which are methods with the same name, but different &lt;em&gt;signatures&lt;/em&gt; when you consider the argument lists and return values. This would be somewhat challenging to support in a dynamic language with very flexible options for method argument handling.&lt;/p&gt;


	&lt;p&gt;You can &amp;#8220;simulate&amp;#8221; overloading by parsing the argument list and taking different paths of execution based on the structure you find. This post discusses how &lt;em&gt;pattern matching&lt;/em&gt;, a hallmark of &lt;em&gt;functional programming&lt;/em&gt;, gives you powerful options.&lt;/p&gt;


	&lt;p&gt;First, let&amp;#8217;s look at a typical example that handles the arguments in an &lt;em&gt;ad hoc&lt;/em&gt; fashion. Consider the following &lt;code&gt;Person&lt;/code&gt; class. You can pass three arguments to the initializer, the &lt;code&gt;first_name&lt;/code&gt;, the &lt;code&gt;last_name&lt;/code&gt;, and the &lt;code&gt;age&lt;/code&gt;. Or, you can pass a hash using the keys &lt;code&gt;:first_name&lt;/code&gt;, &lt;code&gt;:last_name&lt;/code&gt;, and &lt;code&gt;:age&lt;/code&gt;.&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
require "rubygems" 
require "spec" 

class Person
  attr_reader :first_name, :last_name, :age
  def initialize *args
    arg = args[0]
    if arg.kind_of? Hash       # 1
      @first_name = arg[:first_name]
      @last_name  = arg[:last_name]
      @age        = arg[:age]
    else
      @first_name = args[0]
      @last_name  = args[1]
      @age        = args[2]
    end
  end
end

describe "Person#initialize" do 
  it "should accept a hash with key-value pairs for the attributes" do
    person = Person.new :first_name =&amp;gt; "Dean", :last_name =&amp;gt; "Wampler", :age =&amp;gt; 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should accept a first name, last name, and age arguments" do
    person = Person.new "Dean", "Wampler", 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
end
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;The condition on the &lt;code&gt;# 1&lt;/code&gt; comment line checks to see if the first argument is a &lt;code&gt;Hash&lt;/code&gt;. If so, the attribute&amp;#8217;s values are extracted from it. Otherwise, it is assumed that three arguments were specified in a particular order. They are passed to &lt;code&gt;#initialize&lt;/code&gt; in a three-element array. The two &lt;a href="http://rspec.info"&gt;rspec&lt;/a&gt; &lt;em&gt;examples&lt;/em&gt; exercise these behaviors. For simplicity, we ignore some more general cases, as well as error handling.&lt;/p&gt;


	&lt;p&gt;Another approach that is more flexible is to use duck typing, instead. For example, we could replace the line with the &lt;code&gt;# 1&lt;/code&gt; comment with this line:&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
if arg.respond_to? :has_key?
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;There aren&amp;#8217;t many objects that respond to &lt;code&gt;#has_key?&lt;/code&gt;, so we&amp;#8217;re highly confident that we can use &lt;code&gt;[symbol]&lt;/code&gt; to extract the values from the hash.&lt;/p&gt;


	&lt;p&gt;This implementation is fairly straightforward. You&amp;#8217;ve probably written code like this yourself. However, it could get complicated for more involved cases.&lt;/p&gt;


	&lt;h2&gt;Pattern Matching, a Functional Programming Approach&lt;/h2&gt;


	&lt;p&gt;Most programming languages today have &lt;code&gt;switch&lt;/code&gt; or &lt;code&gt;case&lt;/code&gt; statements of some sort and most have support for regular expression matching. However, in &lt;em&gt;functional programming&lt;/em&gt; languages, pattern matching is so important and pervasive that these languages offer very powerful and convenient support for pattern matching.&lt;/p&gt;


	&lt;p&gt;Fortunately, we can get powerful pattern matching, typical of functional languages, in Ruby using the &lt;a href="http://rubyforge.org/frs/?group_id=3690"&gt;Case&lt;/a&gt; gem that is part of the MenTaLguY&amp;#8217;s &lt;a href="http://rubyforge.org/frs/?group_id=3690"&gt;Omnibus Concurrency library&lt;/a&gt;. &lt;code&gt;Omnibus&lt;/code&gt; provides support for the hot &lt;em&gt;Actor model of concurrency&lt;/em&gt;, which Erlang has made famous. However, it would be a shame to restrict the use of the &lt;code&gt;Case&lt;/code&gt; gem to parsing Actor messages. It&amp;#8217;s much more general purpose than that.&lt;/p&gt;


	&lt;p&gt;Let&amp;#8217;s rework our example using the &lt;a href="http://rubyforge.org/frs/?group_id=3690"&gt;Case&lt;/a&gt; gem.&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
require "rubygems" 
require "spec" 
require "case" 

class Person
  attr_reader :first_name, :last_name, :age
  def initialize *args
    case args
    when Case[Hash]       # 1
      arg = args[0]
      @first_name = arg[:first_name]
      @last_name  = arg[:last_name]
      @age        = arg[:age]
    else
      @first_name = args[0]
      @last_name  = args[1]
      @age        = args[2]
    end
  end
end

describe "Person#initialize" do 
  it "should accept a first name, last name, and age arguments" do
    person = Person.new "Dean", "Wampler", 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should accept a has with :first_name =&amp;gt; fn, :last_name =&amp;gt; ln, and :age =&amp;gt; age" do
    person = Person.new :first_name =&amp;gt; "Dean", :last_name =&amp;gt; "Wampler", :age =&amp;gt; 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
end
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;We require the &lt;code&gt;case&lt;/code&gt; gem, which puts the &lt;code&gt;#===&lt;/code&gt; method on steroids. In the &lt;code&gt;when&lt;/code&gt; statement in &lt;code&gt;#initialize&lt;/code&gt;, the expression &lt;code&gt;when Case[Hash]&lt;/code&gt; matches on a one-element array where the element is a &lt;code&gt;Hash&lt;/code&gt;. We extract the key-value pairs as before. The &lt;code&gt;else&lt;/code&gt; clause assumes we have an array for the arguments.&lt;/p&gt;


	&lt;p&gt;So far, this is isn&amp;#8217;t very impressive, but all we did was to reproduce the original behavior. Let&amp;#8217;s extend the example to really exploit some of the neat features of the &lt;code&gt;Case&lt;/code&gt; gem&amp;#8217;s pattern matching. First, let&amp;#8217;s narrow the allowed array values.&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
require "rubygems" 
require "spec" 
require "case" 

class Person
  attr_reader :first_name, :last_name, :age
  def initialize *args
    case args
    when Case[Hash]       # 1
      arg = args[0]
      @first_name = arg[:first_name]
      @last_name  = arg[:last_name]
      @age        = arg[:age]
    when Case[String, String, Integer]
      @first_name = args[0]
      @last_name  = args[1]
      @age        = args[2]
    else
      raise "Invalid arguments: #{args}" 
    end
  end
end

describe "Person#initialize" do 
  it "should accept a first name, last name, and age arguments" do
    person = Person.new "Dean", "Wampler", 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should accept a has with :first_name =&amp;gt; fn, :last_name =&amp;gt; ln, and :age =&amp;gt; age" do
    person = Person.new :first_name =&amp;gt; "Dean", :last_name =&amp;gt; "Wampler", :age =&amp;gt; 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should not accept an array unless it is a [String, String, Integer]" do
    lambda { person = Person.new "Dean", "Wampler", "39" }.should raise_error(Exception)
  end
end
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;The new expression &lt;code&gt;when Case[String, String, Integer]&lt;/code&gt; only matches a three-element array where the first two arguments are strings and the third argument is an integer, which are the types we want. If you use an array with a different number of arguments or the arguments have different types, this &lt;code&gt;when&lt;/code&gt; clause won&amp;#8217;t match. Instead, you&amp;#8217;ll get the default &lt;code&gt;else&lt;/code&gt; clause, which raises an exception. We added another rspec example to test this condition, where the user&amp;#8217;s age was specified as a string instead of as an integer. Of course, you could decide to attempt a conversion of this argument, to make your code more &amp;#8220;forgiving&amp;#8221; of user mistakes.&lt;/p&gt;


	&lt;p&gt;Similarly, what happens if the method supports default values some of the parameters. As written, we can&amp;#8217;t support that option, but let&amp;#8217;s look at a slight variation of &lt;code&gt;Person#initialize&lt;/code&gt;, where a hash of values is not supported, to see what would happen.&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
require "rubygems" 
require "spec" 
require "case" 

class Person
  attr_reader :first_name, :last_name, :age
  def initialize first_name = "Bob", last_name = "Martin", age = 29
    case [first_name, last_name, age]
    when Case[String, String, Integer]
      @first_name = first_name
      @last_name  = last_name
      @age        = age
    else
      raise "Invalid arguments: #{first_name}, #{last_name}, #{age}" 
    end
  end
end

def check person, expected_fn, expected_ln, expected_age
  person.first_name.should == expected_fn
  person.last_name.should  == expected_ln
  person.age.should        == expected_age
end

describe "Person#initialize" do 
  it "should require a first name (string), last name (string), and age (integer) arguments" do
    person = Person.new "Dean", "Wampler", 39
    check person, "Dean", "Wampler", 39
  end
  it "should accept the defaults for all parameters" do
    person = Person.new
    check person, "Bob", "Martin", 29
  end
  it "should accept the defaults for the last name and age parameters" do
    person = Person.new "Dean" 
    check person, "Dean", "Martin", 29
  end
  it "should accept the defaults for the age parameter" do
    person = Person.new "Dean", "Wampler" 
    check person, "Dean", "Wampler", 29
  end
  it "should not accept the first name as a symbol" do
    lambda { person = Person.new :Dean, "Wampler", "39" }.should raise_error(Exception)
  end
  it "should not accept the last name as a symbol" do
  end
  it "should not accept the age as a string" do
    lambda { person = Person.new "Dean", "Wampler", "39" }.should raise_error(Exception)
  end
end
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;We match on all three arguments as an array, asserting they are of the correct type. As you might expect, &lt;code&gt;#initialize&lt;/code&gt; always gets three parameters passed to it, including when default values are used.&lt;/p&gt;


	&lt;p&gt;Let&amp;#8217;s return to our original example, where the object can be constructed with a hash or a list of arguments. There are two more things (at least &amp;#8230;) that we can do. First, we&amp;#8217;re not yet validating the types of the values in the hash. Second, we can use the &lt;code&gt;Case&lt;/code&gt; gem to impose constraints on the values, such as requiring non-empty name strings and a positive age.&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
require "rubygems" 
require "spec" 
require "case" 

class Person
  attr_reader :first_name, :last_name, :age
  def initialize *args
    case args
    when Case[Hash]
      arg = args[0]
      @first_name = arg[:first_name]
      @last_name  = arg[:last_name]
      @age        = arg[:age]
    when Case[String, String, Integer]
      @first_name = args[0]
      @last_name  = args[1]
      @age        = args[2]
    else
      raise "Invalid arguments: #{args}" 
    end
    validate_name @first_name, "first_name" 
    validate_name @last_name, "last_name" 
    validate_age
  end

  protected

  def validate_name name, field_name
    case name
    when Case::All[String, Case.guard {|s| s.length &amp;gt; 0 }]
    else
      raise "Invalid #{field_name}: #{first_name}" 
    end
  end

  def validate_age
    case @age
    when Case::All[Integer, Case.guard {|n| n &amp;gt; 0 }]
    else
      raise "Invalid age: #{@age}" 
    end
  end
end

describe "Person#initialize" do 
  it "should accept a first name, last name, and age arguments" do
    person = Person.new "Dean", "Wampler", 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should accept a has with :first_name =&amp;gt; fn, :last_name =&amp;gt; ln, and :age =&amp;gt; age" do
    person = Person.new :first_name =&amp;gt; "Dean", :last_name =&amp;gt; "Wampler", :age =&amp;gt; 39
    person.first_name.should == "Dean" 
    person.last_name.should  == "Wampler" 
    person.age.should        == 39
  end
  it "should not accept an array unless it is a [String, String, Integer]" do
    lambda { person = Person.new "Dean", "Wampler", "39" }.should raise_error(Exception)
  end
  it "should not accept a first name that is a zero-length string" do
    lambda { person = Person.new "", "Wampler", 39 }.should raise_error(Exception)
  end    
  it "should not accept a first name that is not a string" do
    lambda { person = Person.new :Dean, "Wampler", 39 }.should raise_error(Exception)
  end    
  it "should not accept a last name that is a zero-length string" do
    lambda { person = Person.new "Dean", "", 39 }.should raise_error(Exception)
  end    
  it "should not accept a last name that is not a string" do
    lambda { person = Person.new :Dean, :Wampler, 39 }.should raise_error(Exception)
  end    
  it "should not accept an age that is less than or equal to zero" do
    lambda { person = Person.new "Dean", "Wampler", -1 }.should raise_error(Exception)
    lambda { person = Person.new "Dean", "Wampler", 0 }.should raise_error(Exception)
  end    
  it "should not accept an age that is not an integer" do
    lambda { person = Person.new :Dean, :Wampler, "39" }.should raise_error(Exception)
  end    
end
&lt;/code&gt;
&lt;/pre&gt;

	&lt;p&gt;We have added &lt;code&gt;validate_name&lt;/code&gt; and &lt;code&gt;validate_age&lt;/code&gt; methods that are invoked at the end of &lt;code&gt;#initialize&lt;/code&gt;. In &lt;code&gt;validate_name&lt;/code&gt;, the one &lt;code&gt;when&lt;/code&gt; clause requires &amp;#8220;all&amp;#8221; the conditions to be true, that the name is a string and that it has a non-zero length. Similarly, &lt;code&gt;validate_age&lt;/code&gt; has a &lt;code&gt;when&lt;/code&gt; clause that requires &lt;code&gt;age&lt;/code&gt; to be a positive integer.&lt;/p&gt;


	&lt;h2&gt;Final Thoughts&lt;/h2&gt;


	&lt;p&gt;So, how valuable is this? The code is certainly longer, but it specifies and enforces expected behavior more precisely. The rspec examples verify the enforcement. It smells a little of static typing, which is good or bad, depending on your point of view. ;)&lt;/p&gt;


	&lt;p&gt;Personally, I think the conditional checks are a good way to add robustness in small ways to libraries that will grow and evolve for a long time. The checks document the required behavior for code readers, like new team members, but of course, they should really get that information from the tests. ;) (However, it would be nice to extract the information into the &lt;code&gt;rdocs&lt;/code&gt;.)&lt;/p&gt;


	&lt;p&gt;For small, short-lived projects, I might not worry about the conditional checks as much (but how many times have those &amp;#8220;short-lived projects&amp;#8221; refused to die?).&lt;/p&gt;


	&lt;p&gt;You can read more about &lt;code&gt;Omnibus&lt;/code&gt; and &lt;code&gt;Case&lt;/code&gt; in this &lt;a href="http://www.infoq.com/articles/actors-rubinius-interview"&gt;InfoQ interview&lt;/a&gt; with MenTaLguY. I didn&amp;#8217;t discuss using the Actor model of concurrency, for which these gems were designed. For an example of Actors using Omnibus, see my &lt;a href="http://aspectprogramming.com/papers/BetterRubyThroughFP_v5.pdf"&gt;Better Ruby through Functional Programming&lt;/a&gt; presentation or the &lt;a href="http://confreaks.com/"&gt;Confreak&amp;#8217;s&lt;/a&gt; video of an earlier version of the presentation I gave at last year&amp;#8217;s &lt;a href="http://rubyconf2008.confreaks.com/better-ruby-through-functional-programming-2.html"&gt;RubyConf&lt;/a&gt;.&lt;/p&gt;</description>
      <pubDate>Mon, 16 Mar 2009 19:59:00 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:4e6b0c8e-7b8b-4c8d-b20c-6e30fe39c98c</guid>
      <author>Dean Wampler</author>
      <link>http://blog.objectmentor.com/articles/2009/03/16/tighter-ruby-methods-with-functional-style-pattern-matching-using-the-case-gem</link>
      <category>Dean's Deprecations</category>
      <category>Dynamic Languages</category>
      <category>Design Principles</category>
      <category>Ruby</category>
      <category>methods</category>
      <category>functional</category>
      <category>programming</category>
      <category>pattern</category>
      <category>matching</category>
    </item>
  </channel>
</rss>

