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.
-
A
Hash
key must be aString
type. -
When
nil
is given as a wildcard, it only matches its key.
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:
-
Contains the
name
key. -
The
age
key is anInteger
. -
It has two keys.
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.