Creating a Shared Whiteboard

Credit: James Edward Gray II

Problem

You want to create the network equivalent of a whiteboard. Remote programs can place Ruby objects up on the board, examine objects on the board, or remove objects from the board.

Solution

You could just use a synchronized hash (as in Recipe 16.10), but Rinda[6] provides a data structure called a TupleSpace that is optimized for distributed programming. It works well when you have some clients putting data on the whiteboard, and other clients processing the data and taking it down.

[6] Rinda is a companion library to DRb. Its a Ruby port of the Linda distributed computing environment, which is based on the idea of the tuplespace. Its similar to JavaSpaces.

Lets create an application that lets clients on different parts of the network translate each others sentences, and builds a translation dictionary as they work.

Its easier to see the architecture of the server if you see the clients first, so heres a client that adds some English sentences to a shared TupleSpace:

	#!/usr/bin/ruby -w
	# english_client.rb
	require drb
	require 
inda/tuplespace

	# Connect to the TupleSpace…
	DRb.start_service
	tuplespace = Rinda::TupleSpaceProxy.new(
	 DRbObject.new_with_uri(druby://127.0.0.1:61676)
	)

The English clients job is to split English sentences into words and to add each sentence to the whiteboard as a tuple: [unique id, language, words].

	counter = 0
	DATA.each_line do |line|
	 tuplespace.write([(counter += 1), English, line.strip.split])
	end

	__END__
	Ruby programmers have more fun
	Ruby gurus are obsessed with ducks
	Ruby programmers are happy programmers

Heres a second client. It creates a loop that continually reads all the English sentences from the TupleSpace and puts up word-for-word translations into Pig Latin. It uses Tuplespace#read to read English-language tuples off the whiteboard without removing them.

	require drb
	require 
inda/tuplespace
	require set

	DRb.start_service
	tuplespace = Rinda::TupleSpaceProxy.new(
	 DRbObject.new_with_uri(druby://127.0.0.1:61676)
	)

	# Track of the IDs of the sentences weve translated
	translated = Set.new

	# Continually read English sentences off of the board.
	while english = tuplespace.read([Numeric, English, Array])
	 # Skip anything weve already translated.
	 next if translated.member? english.first
	 translated << english.first

	 # Translate English to Pig Latin.
	 pig_latin = english.last.map do |word|
	 if word =~ /^[aeiou]/i
	 "#{word}way"
	 elsif word =~ /^([^aeiouy]+)(.+)$/i
	 "#{$2}#{$1.downcase}ay"
	 end
	 end

	 # Write the Pig Latin translation back onto the board
 	 tuplespace.write([english.first, Pig Latin, pig_latin])
	end

Finally, heres the language server: the code that exposes a TupleSpace for the two clients to use. It also acts as a third client of the TupleSpace: it continually takes non-English sentences down off of the whiteboard (using the destructive TupleSpace#take method) and matches them word-for-word with the corresponding English sentences (which it also removes from the whiteboard). In this way it gradually builds an English-to-Pig Latin dictionary, which it serializes to disk with YAML:

	#!/usr/bin/ruby -w
	# dictionary_building_server.rb
	require drb
	require yaml
	require 
inda/tuplespace

	# Create a TupleSpace and serve it to the world.
	tuplespace = Rinda::TupleSpace.new
	DRb.start_service(druby://127.0.0.1:61676, tuplespace)

	# Create a dictionary to hold the terms we have seen.
	dictionary = Hash.new
	# Remove non-English sentences from the board.
	while translation = tuplespace.take([Numeric, /^(?!English)/, Array])
	 # Match each with its English equivalent.
	 english = tuplespace.take([translation.first, English, Array])
	 # Match up the words, and save the dictionary.
	 english.last.zip(translation.last) { |en, tr| dictionary[en] = tr }
	 File.open(dictionary.yaml, w) { |file| YAML.dump(dictionary, file) }
	end

If you run the server and then the two clients, the server will spit out a dictionary.yaml file that shows how much it has already learned:


	$ ruby dictionary_building_server.rb &
	$ ruby english_client.rb
	$ ruby pig_latin_client.rb &

	$ cat dictionary.yaml
	---
	happy: appyhay
	programmers: ogrammerspray
	Ruby: ubyray
	gurus: urusgay
	ducks: ucksday
	obsessed: obsessedway
	have: avehay
	are: areway
	fun: unfay
	with: ithway
	more: oremay

Discussion

Rindas TupleSpace class is pretty close to the network equivalent of a whiteboard. A "tuple" is just an ordered sequencein this case, an array of Ruby objects. A TupleSpace holds these sequences and provides an interface to them.

You can add sequences of objects to the TupleSpace using TupleSpace#write. Later, the same or different code can query the object using TupleSpace#read or TupleSpace#take. The only difference is that TupleSpace#take is destructive; it removes the object from the TupleSpace as its read.

You can select certain tuples by passing TupleSpace#read or TupleSpace#take a template that matches the tuples you seek. A template is just another tuple. In the example code, we used templates like [Numeric, English, Array]. Each element of a tuple is matched against the corresponding element of a template with the === operator, the same operator used in Ruby case statements.

That particular template will match any three-element tuple whose first element is a Numeric object, whose second element is the literal string English, and whose third element is an Array object: that is, all the English sentences currently on the whiteboard.

You can create templates containing any kind of object that will work with the === operator: for instance, a Regexp object in a template can match against strings in a tuple. Any nil slot in a template is a wildcard slot that will match anything.

See Also

  • The DRb presentation by Mark Volkmann in the "Why Ruby?" repository at http://rubyforge.org/docman/view.php/251/216/DistributedRuby.pdf has some material on TupleSpaces
  • Clients can also choose to be notified of TupleSpace events; you can see an example at http://ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-talk/159065


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net