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.