dataflow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ == 0.1.0 / 2009-06-13
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Larry Diehl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,172 @@
1
+ h1. What's this?
2
+
3
+ A Ruby library that adds Dataflow variables (inspired by the Oz
4
+ language). Dataflow variables have the property that they can only
5
+ be bound/assigned to once, or have an equivalent value as an existing
6
+ assignment (see "unification").
7
+
8
+ Dataflow variables must be declared before they are used, and can be
9
+ passed around as data without actually being bound. If the variable
10
+ gets used (in this library this means a method call) while being
11
+ unbound then the currently executing thread will suspend.
12
+
13
+ h1. What's the point?
14
+
15
+ Ruby is Object Oriented (with the ability to mutate local, instance,
16
+ class, and global variables, and even constants), and on top of that
17
+ it has powerful reflection and meta-programming abilities. While these
18
+ features are useful for certain problems, they are not within the
19
+ declarative model. Staying in the declarative model gives one 2 advantages:
20
+ # It is easy to reason about what the program does
21
+ # Simple but powerful concurrency is possible
22
+
23
+ Ruby, like many other OO languages, is facing the hurdles of taking
24
+ advantage of the increase of processor cores within a simple parallel
25
+ programming model. This library lets you program Ruby in the
26
+ declarative concurrent model when you need to take advantage of multiple cores
27
+ (assuming a Ruby implementation that uses native threads in one way or
28
+ another).
29
+
30
+ The trick to this kind of programming is binding variables from other
31
+ threads. The nice thing is that many existing
32
+ libraries/classes/methods can still be used, just avoid
33
+ side-effects. Use regular Ruby threading to create threads, use
34
+ "local" or "declare" to create new variables, and use "unify" to bind
35
+ variables.
36
+
37
+ h1. Examples
38
+
39
+ <pre>
40
+ # Local variables
41
+ include Dataflow
42
+
43
+ local do |x, y, z|
44
+ # notice how the order automatically gets resolved
45
+ Thread.new { unify y, x + 2 }
46
+ Thread.new { unify z, y + 3 }
47
+ Thread.new { unify x, 1 }
48
+ z #=> 6
49
+ end
50
+ </pre>
51
+
52
+ <pre>
53
+ # Instance variables
54
+ class AnimalHouse
55
+ include Dataflow
56
+ declare :small_cat, :big_cat
57
+
58
+ def fetch_big_cat
59
+ Thread.new { unify big_cat, small_cat.upcase }
60
+ unify small_cat, 'cat'
61
+ big_cat
62
+ end
63
+ end
64
+
65
+ AnimalHouse.new.fetch_big_cat #=> 'CAT'
66
+ </pre>
67
+
68
+ <pre>
69
+ # Data-driven concurrency
70
+ include Dataflow
71
+
72
+ local do |stream, doubles, triples, squares|
73
+ unify stream, Array.new(5) { Dataflow::Variable.new }
74
+
75
+ Thread.new { unify doubles, stream.map {|n| n*2 } }
76
+ Thread.new { unify triples, stream.map {|n| n*3 } }
77
+ Thread.new { unify squares, stream.map {|n| n**2 } }
78
+
79
+ Thread.new { stream.each {|x| unify x, rand(100) } }
80
+
81
+ puts "original: #{stream.inspect}"
82
+ puts "doubles: #{doubles.inspect}"
83
+ puts "triples: #{triples.inspect}"
84
+ puts "squares: #{squares.inspect}"
85
+ end
86
+ </pre>
87
+
88
+ <pre>
89
+ # By-need trigger laziness
90
+ include Dataflow
91
+
92
+ local do |x, y, z|
93
+ Thread.new { unify y, by_need { 4 } }
94
+ Thread.new { unify z, x + y }
95
+ Thread.new { unify x, by_need { 3 } }
96
+ z #=> 7
97
+ end
98
+ </pre>
99
+
100
+ <pre>
101
+ # Need-later future expressions
102
+ include Dataflow
103
+
104
+ local do |x, y, z|
105
+ unify y, need_later { 4 }
106
+ unify z, need_later { x + y }
107
+ unify x, need_later { 3 }
108
+ z #=> 7
109
+ end
110
+ </pre>
111
+
112
+ h1. Ports using Dataflow
113
+
114
+ Ports are an extension of the declarative concurrent model to support nondeterministic behavior. They accomplish this through the use of a single state variable. Ports are also inspired by the Oz language.
115
+
116
+ An Actor class in the style of Erlang message-passing processes is also provided. It makes use of the asynchronous behavior of ports, but otherwise uses no state variables.
117
+
118
+ h1. Examples using Ports
119
+
120
+ <pre>
121
+ include Dataflow
122
+
123
+ local do |port, stream|
124
+ unify port, Dataflow::Port.new(stream)
125
+ Thread.new {port.send 2}
126
+ Thread.new {port.send 8}
127
+ Thread.new {port.send 1024}
128
+ stream.take(3).sort #=> [2, 8, 1024]
129
+ end
130
+ </pre>
131
+
132
+ h1. Examples using Actors
133
+
134
+ <pre>
135
+ include Dataflow
136
+
137
+ Ping = Actor.new {
138
+ 3.times {
139
+ case receive
140
+ when "Ping"
141
+ puts "Ping"
142
+ Pong.send "Pong"
143
+ end
144
+ }
145
+ }
146
+
147
+ Pong = Actor.new {
148
+ 3.times {
149
+ case receive
150
+ when "Pong"
151
+ puts "Pong"
152
+ Ping.send "Ping"
153
+ end
154
+ }
155
+ }
156
+
157
+ Actor.new { Ping.send "Ping" }
158
+
159
+ Ping.join
160
+ Pong.join
161
+ </pre>
162
+
163
+ h1. References
164
+
165
+ The basis of dataflow variables around a language is not common among
166
+ popular languages and may be confusing to some. For an in-depth
167
+ introduction to the Oz language and the techniques used in this
168
+ library (including by_need triggers, port objects, and comparisons to Erlang message passing) see the book "Concepts, Techniques, and Models of Computer Programming":http://en.wikipedia.org/wiki/Concepts,_Techniques,_and_Models_of_Computer_Programming
169
+
170
+ h1. Contributors
171
+
172
+ larrytheliquid, amiller
@@ -0,0 +1,35 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/clean"
4
+ require "spec/rake/spectask"
5
+ require File.expand_path("./dataflow")
6
+
7
+ Spec::Rake::SpecTask.new do |t|
8
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
9
+ end
10
+
11
+ desc "Run the specs"
12
+ task :default => :spec
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = "dataflow"
16
+ s.rubyforge_project = s.name
17
+ s.version = Dataflow::VERSION
18
+ s.author = "Larry Diehl"
19
+ s.email = "larrytheliquid" + "@" + "gmail.com"
20
+ s.homepage = "http://github.com/larrytheliquid/dataflow"
21
+ s.summary = "Dataflow concurrency for Ruby (inspired by the Oz language)"
22
+ s.description = s.summary
23
+ s.files = %w[LICENSE History.txt Rakefile README.textile dataflow.rb] + Dir["lib/**/*"] + Dir["examples/**/*"]
24
+ s.test_files = Dir["spec/**/*"]
25
+ end
26
+
27
+ Rake::GemPackageTask.new(spec) do |package|
28
+ package.gem_spec = spec
29
+ end
30
+
31
+ desc 'Install the package as a gem.'
32
+ task :install => [:clean, :package] do
33
+ gem = Dir['pkg/*.gem'].first
34
+ sh "sudo gem install --no-rdoc --no-ri --local #{gem}"
35
+ end
@@ -0,0 +1,90 @@
1
+ require 'monitor'
2
+
3
+ module Dataflow
4
+ VERSION = "0.1.0"
5
+
6
+ def self.included(cls)
7
+ class << cls
8
+ def declare(*readers)
9
+ readers.each do |name|
10
+ class_eval <<-RUBY
11
+ def #{name}
12
+ return @__dataflow_#{name}__ if defined? @__dataflow_#{name}__
13
+ Variable::LOCK.synchronize { @__dataflow_#{name}__ ||= Variable.new }
14
+ end
15
+ RUBY
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def local(&block)
22
+ vars = Array.new(block.arity) { Variable.new }
23
+ block.call *vars
24
+ end
25
+
26
+ def unify(variable, value)
27
+ variable.__unify__ value
28
+ end
29
+
30
+ def by_need(&block)
31
+ Variable.new &block
32
+ end
33
+
34
+ def need_later(&block)
35
+ local do |future|
36
+ Thread.new { unify future, block.call }
37
+ future
38
+ end
39
+ end
40
+
41
+ # Note that this class uses instance variables directly rather than nicely
42
+ # initialized instance variables in get/set methods for memory and
43
+ # performance reasons
44
+ class Variable
45
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
46
+ LOCK = Monitor.new
47
+ def initialize(&block) @__trigger__ = block if block_given? end
48
+
49
+ # Lazy-load conditions to be nice on memory usage
50
+ def __binding_condition__() @__binding_condition__ ||= LOCK.new_cond end
51
+
52
+ def __unify__(value)
53
+ LOCK.synchronize do
54
+ if @__bound__
55
+ raise UnificationError if @__value__ != value
56
+ else
57
+ @__value__ = value
58
+ @__bound__ = true
59
+ __binding_condition__.broadcast # wakeup all method callers
60
+ remove_instance_variable :@__binding_condition__ # GC
61
+ end
62
+ end
63
+ @__value__
64
+ end
65
+
66
+ def __activate_trigger__
67
+ @__value__ = @__trigger__.call
68
+ @__bound__ = true
69
+ remove_instance_variable :@__trigger__ # GC
70
+ end
71
+
72
+ def method_missing(name, *args, &block)
73
+ LOCK.synchronize do
74
+ unless @__bound__
75
+ if @__trigger__
76
+ __activate_trigger__
77
+ else
78
+ __binding_condition__.wait
79
+ end
80
+ end
81
+ end unless @__bound__
82
+ @__value__.__send__(name, *args, &block)
83
+ end
84
+ end
85
+
86
+ UnificationError = Class.new StandardError
87
+ end
88
+
89
+ require "#{File.dirname(__FILE__)}/lib/port"
90
+ require "#{File.dirname(__FILE__)}/lib/actor"
@@ -0,0 +1,17 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ local do |stream, doubles, triples, squares|
5
+ unify stream, Array.new(5) { Dataflow::Variable.new }
6
+
7
+ Thread.new { unify doubles, stream.map {|n| n*2 } }
8
+ Thread.new { unify triples, stream.map {|n| n*3 } }
9
+ Thread.new { unify squares, stream.map {|n| n**2 } }
10
+
11
+ Thread.new { stream.each {|x| unify x, rand(100) } }
12
+
13
+ puts "original: #{stream.inspect}"
14
+ puts "doubles: #{doubles.inspect}"
15
+ puts "triples: #{triples.inspect}"
16
+ puts "squares: #{squares.inspect}"
17
+ end
@@ -0,0 +1,13 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ require 'net/http'
3
+ include Dataflow
4
+
5
+ # Be gentle running this one
6
+ Thread.abort_on_exception = true
7
+ local do |stream, branding_occurences, number_of_mentions|
8
+ unify stream, Array.new(10) { Dataflow::Variable.new }
9
+ stream.each {|s| Thread.new(s) {|v| unify v, Net::HTTP.get_response(URI.parse("http://www.cuil.com/search?q=#{rand(1000)}")).body } }
10
+ Thread.new { unify branding_occurences, stream.map {|http_body| http_body.scan /cuil/ } }
11
+ Thread.new { unify number_of_mentions, branding_occurences.map {|occurences| occurences.length } }
12
+ puts number_of_mentions.inspect
13
+ end
@@ -0,0 +1,12 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ require 'net/http'
3
+ include Dataflow
4
+
5
+ # Be gentle running this one
6
+ Thread.abort_on_exception = true
7
+ local do |stream, branding_occurences, number_of_mentions|
8
+ unify stream, Array.new(10) { need_later { Net::HTTP.get_response(URI.parse("http://www.cuil.com/search?q=#{rand(1000)}")).body } }
9
+ unify branding_occurences, need_later { stream.map {|http_body| http_body.scan /cuil/ } }
10
+ unify number_of_mentions, need_later { branding_occurences.map {|occurences| occurences.length } }
11
+ puts number_of_mentions.inspect
12
+ end
@@ -0,0 +1,15 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+
3
+ class AnimalHouse
4
+ include Dataflow
5
+ declare :small_cat, :big_cat
6
+
7
+ def fetch_big_cat
8
+ Thread.new { unify big_cat, small_cat.upcase }
9
+ unify small_cat, 'cat'
10
+ big_cat
11
+ end
12
+ end
13
+
14
+ puts AnimalHouse.new.fetch_big_cat
15
+
@@ -0,0 +1,9 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ local do |x, y, z|
5
+ Thread.new { unify y, by_need { 4 } }
6
+ Thread.new { unify z, x + y }
7
+ Thread.new { unify x, by_need { 3 } }
8
+ puts z
9
+ end
@@ -0,0 +1,11 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ local do |x, y, z|
5
+ # notice how the order automatically gets resolved
6
+ Thread.new { unify y, x + 2 }
7
+ Thread.new { unify z, y + 3 }
8
+ Thread.new { unify x, 1 }
9
+ puts z
10
+ end
11
+
@@ -0,0 +1,26 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ Ping = Actor.new {
5
+ 3.times {
6
+ case receive
7
+ when "Ping"
8
+ puts "Ping"
9
+ Pong.send "Pong"
10
+ end
11
+ }
12
+ }
13
+
14
+ Pong = Actor.new {
15
+ 3.times {
16
+ case receive
17
+ when "Pong"
18
+ puts "Pong"
19
+ Ping.send "Ping"
20
+ end
21
+ }
22
+ }
23
+
24
+ Ping.send "Ping"
25
+ Ping.join
26
+ Pong.join
@@ -0,0 +1,13 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ require 'net/http'
3
+ include Dataflow
4
+
5
+ # Be gentle running this one
6
+ Thread.abort_on_exception = true
7
+ local do |port, stream, branding_occurences, number_of_mentions|
8
+ unify port, Dataflow::Port.new(stream)
9
+ 10.times { Thread.new(port) {|p| p.send Net::HTTP.get_response(URI.parse("http://www.cuil.com/search?q=#{rand(1000)}")).body } }
10
+ Thread.new { unify branding_occurences, stream.take(10).map {|http_body| http_body.scan /cuil/ } }
11
+ Thread.new { unify number_of_mentions, branding_occurences.map {|occurences| occurences.length } }
12
+ puts number_of_mentions.inspect
13
+ end
@@ -0,0 +1,10 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ local do |port, stream|
5
+ unify port, Dataflow::Port.new(stream)
6
+ Thread.new {port.send 2}
7
+ Thread.new {port.send 8}
8
+ Thread.new {port.send 1024}
9
+ puts stream.take(3).sort.inspect
10
+ end
@@ -0,0 +1,21 @@
1
+ require "#{File.dirname(__FILE__)}/../dataflow"
2
+ include Dataflow
3
+
4
+ # Send M messages each along a ring of N nodes
5
+ N = 4
6
+ M = 2
7
+ actors = Array.new(N) { Dataflow::Variable.new }
8
+
9
+ N.times do |n|
10
+ unify actors[n], Actor.new {
11
+ M.times do |m|
12
+ receive
13
+ puts "[#{n} #{m}]"
14
+ actors[(n+1) % N].send :msg
15
+ end
16
+ puts "[#{n}] done"
17
+ }
18
+ end
19
+
20
+ actors.first.send :msg
21
+ actors.each { |x| x.join }
@@ -0,0 +1,22 @@
1
+ module Dataflow
2
+ class Actor < Thread
3
+ def initialize(&block)
4
+ @stream = Variable.new
5
+ @port = Port.new(@stream)
6
+ # Run this block in a new thread
7
+ super { instance_eval &block }
8
+ end
9
+
10
+ def send message
11
+ @port.send message
12
+ end
13
+
14
+ private
15
+
16
+ def receive
17
+ result = @stream.head
18
+ @stream = @stream.tail
19
+ result
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ require 'thread'
2
+
3
+ module Dataflow
4
+ class Port
5
+ include Dataflow
6
+ LOCK = Mutex.new
7
+
8
+ class Stream
9
+ include Dataflow
10
+ declare :head, :tail
11
+
12
+ # Defining each allows us to use the enumerable mixin
13
+ # None of the list can be garbage collected less the head is
14
+ # garbage collected, so it will grow indefinitely even though
15
+ # the function isn't recursive.
16
+ include Enumerable
17
+ def each
18
+ s = self
19
+ loop do
20
+ yield s.head
21
+ s = s.tail
22
+ end
23
+ end
24
+
25
+ # Backported Enumerable#take for any 1.8.6 compatible Ruby
26
+ unless method_defined?(:take)
27
+ def take(num)
28
+ result = []
29
+ each_with_index do |x, i|
30
+ return result if num == i
31
+ result << x
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Create a stream object, bind it to the input variable
38
+ # Instance variables are necessary because @end is state
39
+ def initialize(x)
40
+ @end = Stream.new
41
+ unify x, @end
42
+ end
43
+
44
+ # This needs to be synchronized because it uses @end as state
45
+ def send value
46
+ LOCK.synchronize do
47
+ unify @end.head, value
48
+ unify @end.tail, Stream.new
49
+ @end = @end.tail
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,29 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+ Thread.abort_on_exception = true
3
+
4
+ describe 'Syncronously sending to an Actor' do
5
+ it 'passes in each message received and preserves order' do
6
+ local do |port, stream, actor|
7
+ unify port, Dataflow::Port.new(stream)
8
+ unify actor, Dataflow::Actor.new { 3.times { port.send receive } }
9
+ actor.send 1
10
+ actor.send 2
11
+ stream.take(2).should == [1, 2]
12
+ actor.send 3
13
+ stream.take(3).should == [1, 2, 3]
14
+ end
15
+ end
16
+ end
17
+
18
+ describe 'Asyncronously sending to an Actor' do
19
+ it 'passes in each message received and preserves order' do
20
+ local do |port, stream, actor|
21
+ unify port, Dataflow::Port.new(stream)
22
+ unify actor, Dataflow::Actor.new { 3.times { port.send receive } }
23
+ Thread.new {actor.send 2}
24
+ Thread.new {actor.send 8}
25
+ Thread.new {actor.send 1024}
26
+ stream.take(3).sort.should == [2, 8, 1024]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe 'A by_need trigger' do
4
+ it 'creates a need when a method is called on it' do
5
+ local do |x, y, z|
6
+ Thread.new { unify y, by_need { 4 } }
7
+ Thread.new { unify z, x + y }
8
+ Thread.new { unify x, by_need { 3 } }
9
+ z.should == 7
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,128 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ context 'Using "local" for local variables' do
4
+ describe 'An unbound Variable' do
5
+ it 'suspends if an unbound variable has a method called on it until it is bound' do
6
+ local do |big_cat, small_cat|
7
+ Thread.new { unify big_cat, small_cat.upcase }
8
+ unify small_cat, 'cat'
9
+ big_cat.should == 'CAT'
10
+ end
11
+ end
12
+
13
+ it 'suspends if an unbound variable has a method called on it until it is bound with nil' do
14
+ local do |is_nil, var_nil|
15
+ Thread.new { unify is_nil, var_nil.nil? }
16
+ unify var_nil, nil
17
+ is_nil.should be_true
18
+ end
19
+ end
20
+
21
+ it 'suspends if an unbound variable has a method called on it until it is bound (with nested local variables)' do
22
+ local do |small_cat|
23
+ local do |big_cat|
24
+ Thread.new { unify big_cat, small_cat.upcase }
25
+ unify small_cat, 'cat'
26
+ big_cat.should == 'CAT'
27
+ end
28
+ end
29
+ end
30
+
31
+ it 'performs order-determining concurrency' do
32
+ local do |x, y, z|
33
+ Thread.new { unify y, x + 2 }
34
+ Thread.new { unify z, y + 3 }
35
+ Thread.new { unify x, 1 }
36
+ z.should == 6
37
+ end
38
+ end
39
+
40
+ it 'binds on unification' do
41
+ local do |animal|
42
+ unify animal, 'cat'
43
+ animal.should == 'cat'
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'A bound Variable' do
50
+ it 'does not complain when unifying with an equal object' do
51
+ lambda do
52
+ local do |animal|
53
+ unify animal, 'cat'
54
+ unify animal, 'cat'
55
+ end
56
+ end.should_not raise_error
57
+ end
58
+
59
+ it 'does not complain when unifying with an unequal object when shadowing' do
60
+ lambda do
61
+ local do |animal|
62
+ unify animal, 'cat'
63
+ local do |animal|
64
+ unify animal, 'dog'
65
+ end
66
+ end
67
+ end.should_not raise_error
68
+ end
69
+
70
+ it 'complains when unifying with an unequal object' do
71
+ lambda do
72
+ local do |animal|
73
+ unify animal, 'cat'
74
+ unify animal, 'dog'
75
+ end
76
+ end.should raise_error(Dataflow::UnificationError)
77
+ end
78
+ end
79
+
80
+ context 'Using "declare" for object-specific read-only attributes' do
81
+ class Store
82
+ include Dataflow
83
+ declare :animal, :big_cat, :small_cat, :x, :y, :z, :is_nil, :var_nil
84
+ end
85
+ before { @store = Store.new }
86
+
87
+ describe 'An unbound Variable' do
88
+ it 'suspends if an unbound variable has a method called on it until it is bound' do
89
+ Thread.new { unify @store.big_cat, @store.small_cat.upcase }
90
+ unify @store.small_cat, 'cat'
91
+ @store.big_cat.should == 'CAT'
92
+ end
93
+
94
+ it 'suspends if an unbound variable has a method called on it until it is bound with nil' do
95
+ Thread.new { unify @store.is_nil, @store.var_nil.nil? }
96
+ unify @store.var_nil, nil
97
+ @store.is_nil.should be_true
98
+ end
99
+
100
+ it 'performs order-determining concurrency' do
101
+ Thread.new { unify @store.y, @store.x + 2 }
102
+ Thread.new { unify @store.z, @store.y + 3 }
103
+ Thread.new { unify @store.x, 1 }
104
+ @store.z.should == 6
105
+ end
106
+
107
+ it 'binds on unification' do
108
+ unify @store.animal, 'cat'
109
+ @store.animal.should == 'cat'
110
+ end
111
+ end
112
+
113
+ describe 'A bound Variable' do
114
+ it 'does not complain when unifying with an equal object' do
115
+ lambda do
116
+ unify @store.animal, 'cat'
117
+ unify @store.animal, 'cat'
118
+ end.should_not raise_error
119
+ end
120
+
121
+ it 'complains when unifying with an unequal object' do
122
+ lambda do
123
+ unify @store.animal, 'cat'
124
+ unify @store.animal, 'dog'
125
+ end.should raise_error(Dataflow::UnificationError)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,12 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe 'A need_later expression' do
4
+ it 'returns a future to be calculated later' do
5
+ local do |x, y, z|
6
+ unify y, need_later { 4 }
7
+ unify z, need_later { x + y }
8
+ unify x, need_later { 3 }
9
+ z.should == 7
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe 'Syncronously sending to a Port' do
4
+ it 'extends the length of the stream and preserves order' do
5
+ local do |port, stream|
6
+ unify port, Dataflow::Port.new(stream)
7
+ port.send 1
8
+ port.send 2
9
+ stream.take(2).should == [1, 2]
10
+ port.send 3
11
+ stream.take(3).should == [1, 2, 3]
12
+ end
13
+ end
14
+ end
15
+
16
+ describe 'Asyncronously sending to a Port' do
17
+ it 'extends the length of the stream and does not preserve order' do
18
+ local do |port, stream|
19
+ unify port, Dataflow::Port.new(stream)
20
+ Thread.new {port.send 2}
21
+ Thread.new {port.send 8}
22
+ Thread.new {port.send 1024}
23
+ stream.take(3).sort.should == [2, 8, 1024]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'dataflow'
4
+
5
+ Spec::Runner.configure do |config|
6
+ config.include Dataflow
7
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dataflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Larry Diehl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Dataflow concurrency for Ruby (inspired by the Oz language)
17
+ email: larrytheliquid@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - LICENSE
26
+ - History.txt
27
+ - Rakefile
28
+ - README.textile
29
+ - dataflow.rb
30
+ - lib/actor.rb
31
+ - lib/port.rb
32
+ - examples/local_variables.rb
33
+ - examples/dataflow_http_gets.rb
34
+ - examples/instance_variables.rb
35
+ - examples/data_driven.rb
36
+ - examples/ring.rb
37
+ - examples/port_send.rb
38
+ - examples/laziness.rb
39
+ - examples/port_http_gets.rb
40
+ - examples/future_http_gets.rb
41
+ - examples/messages.rb
42
+ has_rdoc: false
43
+ homepage: http://github.com/larrytheliquid/dataflow
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project: dataflow
64
+ rubygems_version: 1.3.1
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Dataflow concurrency for Ruby (inspired by the Oz language)
68
+ test_files:
69
+ - spec/need_later_spec.rb
70
+ - spec/by_need_spec.rb
71
+ - spec/spec_helper.rb
72
+ - spec/port_spec.rb
73
+ - spec/spec.opts
74
+ - spec/dataflow_spec.rb
75
+ - spec/actor_spec.rb