The dRuby Book

7.3 Expressing a Tuple with Hash

All the tuples you’ve seen so far are based on Array, which is the same in Linda. I’ve also implemented Hash-based tuples. Contrary to an Array tuple, a Hash tuple offers an easier way to represent a tuple’s semantics. In an Array tuple, the semantics of the tuple are represented by the order of its elements. In a Hash tuple, you can use a key for the purpose. All methods that write tuples into the tuplespace—such as write, read, and notify—provide a Hash tuple as well as an Array tuple. The pattern matching rule of a Hash-based tuple is almost the same as that of an Array-based tuple, but there are a few restrictions.

Here are some Hash tuple examples:

  ​{"name" => "seki", "age" => 0x20}​
  ​{"kind" => "family", "name" => "Elinor", "sister" => "Carolyn"}​
  ​{"request" => "fact", "lower" => 1, "upper" => 10 }​
  ​{"answer" => "fact", "lower" => 1, "upper" => 10, "value" => 3628800 }​

If a non-String key is given to a Hash tuple, a Rinda::InvalidHashTupleKey exception is raised. Here’s a pattern matching example:

  ​Pattern : {"name" => nil, "age" => Integer}​

The preceding pattern has the following meanings:

Let’s see which tuples match the preceding pattern:

  ​1.{"name" => "m_seki", "age" => 32.5} # ×
  ​2.{"name" => "seki", "age" => 0x20} # ○
  ​3.{"name" => "seki", "age" => 0x20, "url" => "http://www.druby.org"} # ×
  ​4.{"age" => 0x20 } # ×

Tuple 1 fails because it has correct keys, but age has a Float value, not Integer.

Tuple 2 succeeds because it has correct keys and because age has an Integer value.

Tuple 3 fails because it has a different number of keys.

Tuple 4 fails because it doesn’t have a name key. Even when a wildcard pattern is passed, it at least has to have the key.

Let’s rewrite the factorial service we wrote in Figure 33, Expressing the factorial request tuple and the result tuple as a service using a Hash tuple. Here are a request tuple and a response tuple:

  ​{ "request" => "fact", "range" => Range }​
  ​{ "answer" => "fact", "range" => Range, "fact" => Integer }​

The Hash tuple version has a more semantic meaning than the Array tuple version. The following is the client code:

ts01c2h.rb
  ​require 'drb/drb'
  ​​
  def fact_client(ts, a, b, n=1000)​
  ​ req = []​
  ​ a.step(b, n) { |head|​
  ​ tail = [b, head + n - 1].min​
  ​ range = (head..tail)​
  ​ req.push(range)​
  ​ ts.write({"request"=>"fact", "range"=>range})​
  ​ }​
  ​​
  ​ req.inject(1) { |value, range|​
  ​ tuple = ts.take({"answer"=>"fact", "range"=>range, "fact"=>Integer})​
  ​ value * tuple["fact"]​
  ​ }​
  end
  ​​
  ​ts_uri = ARGV.shift || 'druby://localhost:12345'
  ​DRb.start_service​
  ​$ts = DRbObject.new_with_uri(ts_uri)​
  ​p fact_client($ts, 1, 20000)​

And the following is the server code:

ts01sh.rb
  ​require 'drb/drb'
  class FactServer​
  def initialize(ts)​
  ​ @ts = ts​
  end
  def main_loop​
  ​ loop do
  ​ tuple = @ts.take({"request"=>"fact", "range"=>Range})​
  ​ value = tuple["range"].inject(1) { |a, b| a * b }​
  ​ @ts.write({"answer"=>"fact", "range"=>tuple["range"], "fact"=>value})​
  end
  end
  end
  ​​
  ​ts_uri = ARGV.shift || 'druby://localhost:12345'
  ​DRb.start_service​
  ​$ts = DRbObject.new_with_uri(ts_uri)​
  ​FactServer.new($ts).main_loop​

Instead of calling tuple[3], now you can call it as tuple["range"], which is easier to understand.