dataflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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