I've been considering the ways to add a particular language construct
(data flow variables) to Ruby. I've come up with, and implemented,
one method of doing this, but it relies on global variables (I
mentioned this on the list a month ago or so), so it's not something I
really want to stick with.
Another way I've imagined doing it would involve changing the way
assignment works for instances of a Class while behaving more nicely.
I'm envisioning something like this:
foo = DataFlow.new
bar = Thread.new(foo) {|fooinarr| print fooinarr[0]}
baz = Thread.new(foo) {|fooinarr| sleep(3);fooinarr[0] = "Hello World,
sorry I'm late"}
Or, alternatively
foo = DataFlow.new
Thread.new(foo) {|fooinarr| begin(fooinarr[0]} #Starts a time
consuming, but autonomous job
#Lots of user interaction
foo = gets
#Lots more interaction
I know these examples aren't the greatest, but trust me, there's
better stuff you can do.
In any case, to make this possible, it would require changing the way
that variabe assignment works. I realize that this isn't something
you can do in pure Ruby, but I'm curious if anyone knows of a way to
do it at all, possibly limiting the scope of the change to just
members of DataFlow so that they can act as wrappers for objects
assigned to foo.
Feel free to berate me if this is an unrealistic expectation.
--
-Dan Nugent
> Hello,
>
> I've been considering the ways to add a particular language construct
> (data flow variables) to Ruby. I've come up with, and implemented,
> one method of doing this, but it relies on global variables (I
> mentioned this on the list a month ago or so), so it's not something I
> really want to stick with.
>
> Another way I've imagined doing it would involve changing the way
> assignment works for instances of a Class while behaving more nicely.
>
> I'm envisioning something like this:
>
> foo = DataFlow.new
>
> bar = Thread.new(foo) {|fooinarr| print fooinarr[0]}
> baz = Thread.new(foo) {|fooinarr| sleep(3);fooinarr[0] = "Hello World, sorry I'm late"}
you'll have to come up with something more challenging that that ;-) :
harp:~ > cat a.rb
require 'thread'
class DataFlow
def slots
@slots ||= Hash::new{|h,k| h[k] = Queue::new}
end
def [] idx
slots[ idx ].pop
end
def []= idx, val
slots[ idx ].push val
end
end
foo = DataFlow::new
bar = Thread::new(foo){|fooinarr| puts fooinarr[0]}
baz = Thread::new(foo){|fooinarr| sleep(3) and fooinarr[0] = "Hello World, sorry I'm late"}
bar.join
baz.join
harp:~ > ruby a.rb
Hello World, sorry I'm late
i'm unclear as you __exactly__ what you can't do that you want to. maybe you
don't need to change anything to get what you want. can you elaborate?
kind regards.
-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================
> Hello,
>
> I've been considering the ways to add a particular language construct
> (data flow variables) to Ruby.
Maybe matju's gridflow does this already?
http://gridflow.ca/latest/doc/
--
Eric Hodel - drb...@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04
foo = DataFlow.new
bar = Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
" + params[0]}
baz = Thread.new(foo, "Sleepy Thread") do |params|
print params[1] + ": Going to sleep."
sleep(3);
params[0] = "Hello World, sorry I'm late"
end
Eric: As I understand it, that might be a little different, although
it's probablly worth looking into. Would you happen to know what a
good entry point to the documentation is?
--
-Dan Nugent
foo = DataFlow.new
bar = Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
" + params[0]}
sleep(3)
baz = Thread.new(foo, "Sleepy Thread") do |params|
print params[1] + ": Going to sleep."
sleep(3);
params[0] = "Hello World, sorry I'm late"
end
--output--
[3 second pause]
"Sleepy Thread: Going to sleep."
[3 second pause]
"Monitor Thread: Hello world, sorry I'm late"
--
-Dan Nugent
> On 10/28/05, Eric Hodel <drb...@segment7.net> wrote:
>
>> On Oct 28, 2005, at 1:49 PM, Daniel Nugent wrote:
>>
>>> Hello,
>>>
>>> I've been considering the ways to add a particular language
>>> construct
>>> (data flow variables) to Ruby.
>>
>> Maybe matju's gridflow does this already?
>>
>> http://gridflow.ca/latest/doc/
>
> As I understand it, that might be a little different, although
> it's probablly worth looking into. Would you happen to know what a
> good entry point to the documentation is?
matju reads this list, so he just might pick it up...
> Bah, that example wasn't good either....
>
> foo = DataFlow.new
>
> bar = Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
> " + params[0]}
>
> sleep(3)
>
> baz = Thread.new(foo, "Sleepy Thread") do |params|
> print params[1] + ": Going to sleep."
> sleep(3);
> params[0] = "Hello World, sorry I'm late"
> end
harp:~ > cat a.rb
require 'thread'
STDOUT.sync = true
class DataFlow
def slots
@slots ||= Hash::new{|h,k| h[k] = Queue::new}
end
def [] idx
slots[ idx ].pop
end
def []= idx, val
slots[ idx ].push val
end
end
counter = Thread::new{ 42.times{|i| puts i; sleep 1} }
foo = DataFlow::new
bar = Thread::new(foo, "Monitor Thread"){|df, label| puts "#{ label } : #{ df[0] }"}
sleep 3
baz = Thread::new(foo, "Sleepy Thread") do |df, label|
puts "#{ label } : Going to sleep."
sleep 3
df[0] = "Hello World, sorry I'm late"
end
bar.join
baz.join
> --output--
> [3 second pause]
> "Sleepy Thread: Going to sleep."
> [3 second pause]
> "Monitor Thread: Hello world, sorry I'm late"
harp:~ > ruby a.rb
0
1
2
Sleepy Thread : Going to sleep.
3
4
5
Monitor Thread : Hello World, sorry I'm late
i still don't understand your problem? sorry! ;-)
The thing that makes the DFVs interesting is that the code you're
running wouldn't have to know it's being run in parallel to use the
Dataflow variables properly. It would just use them and be cool with
it.
It is a bit of an esoteric concern, I'll admit. But I wanna see if I
can make it work.
See the edit to your attempt:
On 10/28/05, Ara.T.Howard <Ara.T....@noaa.gov> wrote:
> harp:~ > cat a.rb
> require 'thread'
>
> STDOUT.sync = true
>
> counter = Thread::new{ 42.times{|i| puts i; sleep 1} }
>
> foo = DataFlow::new
>
+ bar = Thread::new(foo, "Monitor Thread"){|df, label| puts "#{
label } : #{ df }"}
>
> sleep 3
>
> baz = Thread::new(foo, "Sleepy Thread") do |df, label|
> puts "#{ label } : Going to sleep."
> sleep 3
+ df = "Hello World, sorry I'm late"
> end
>
> bar.join
> baz.join
>
>
> harp:~ > ruby a.rb
> 0
> 1
> 2
> Sleepy Thread : Going to sleep.
> 3
> 4
> 5
> Monitor Thread : Hello World, sorry I'm late
--
-Dan Nugent
> It was a good shot. The thing is that DataFlow is not a collection.
> It would be a wrapper around a single object assigned to it that
> pauses the thread and forces it to pass when it has not been assigned
> a value.
all you need to do for that to work is is a method
class Dataflow
def queue
@queue ||= Queue::new
end
def value
queue.pop
end
def value= v
queue.push v
end
end
and then use
bar = Thread::new(foo, "Monitor Thread"){|df, label| puts "#{ label } : #{ df.value }"}
baz = Thread::new(foo, "Sleepy Thread") do |df, label|
puts "#{ label } : Going to sleep."
sleep 3
df.value = "Hello World, sorry I'm late"
end
although this approach, or the one your suggesting (though you cannot change
rhs behvaiour in ruby) seems silly to me: i've written many data flow modules
and it seems pretty useless to have a flow with only one input/output - which
you are limited to with this method of setting the value via a single method
or assignment. i would generally consider a 'flow' to be a directed acyclic
graph of job dependacies - a tree - where some jobs can be run in parallel and
some cannot. my flow library descibes this like
#
# a and b depend on nothing, c depends on a and b, d depends on c
#
flow = Flow::new a => nil, b => nil, c => [a, b], d => [c]
here c is going to need the results of both a and b to begin. so here you
need slots and then there is no problem. i realize you could make a, b, c,
and d have the behaviour you are talking about - but thus far you've described
putting it in the flow object itself...
> The thing that makes the DFVs interesting is that the code you're running
> wouldn't have to know it's being run in parallel to use the Dataflow
> variables properly. It would just use them and be cool with it.
>
> It is a bit of an esoteric concern, I'll admit. But I wanna see if I can
> make it work.
sure. but this has nothing to with rhs behaviour... it seems like you're
entire concern has nothing to do with global objects, flows, or threads and
everything to do with if ruby can do this:
x = SomeObject::new
x = 42
and have x still be an instance of SomeObject - which is to say, in simple
terms, that you want to override the assignment operator.
to this is the answer is that you cannot, but you can easily come up with a
syntax that looks very nice like
x SomeObject::new
x 42
or
x = SomeObject::new
x[42]
so approaches exist for typing one or two fewer chars, but none of them
involve typing ' = '. ;-)
regards.
I understand perfectly that this isn't something you can do in regular
Ruby. However, I'm interested in seeing if I can make it work
assuming I go and start screwing around with RHS semantics (I'm
thinking by adding something to the Runtime environment that checks if
an object is an instance of this special class).
I got the whole idea for this because we use the language Oz in a
Programming Languages class I'm in. One of the things that Oz has is
a single assignment store that supports data flow behavior. The basic
behavioral sematics of Oz is declarational, which makes a single
assignment store a good thing to have. Ruby, I realize, is
procedural, but I still felt that supporting dataflow behavior in
variables would be an interesting excercise.
Of the ways that I thought it could be done, one of them involved
mucking with global variables wrapped inside of BlankObjects, the
other was adding a keyword to change RHS, and the last was creating an
object with special RHS.
Of those, I like the last one the best.
This isn't for a practical application, I don't intend to actually use
it for any sort of mad science. I'm more like a kid that decides to
build a nuclear reactor in his basement, but with less radiation.
In any case, thanks for trying to work it out.
--
-Dan Nugent
You might want to take a look at RHDL
( http://www.aracnet.com/~ptkwt/ruby_stuff/RHDL/ ).
HDL's are essentially dataflow langauges, I implemented an HDL using ruby
(hence RHDL). Take a look at the Signal class defined there. They can
accecpt values from different Processes which are running independently.
Initially I had a similar problem with assignment. I resolved it by
introducing an assign method on Signal's (or operator << for short).
Note that process in this context is not an OS process, but I suppose they
could be.
Here's an example of a WashMachine state machine written in RHDL:
#begin washmachine:
require 'RHDL'
class WashMachine < RHDL::Design
include RHDL
def initialize(clk,rst)
super()
state_sig = Signal(StateType(:start,:wash,:rinse,:spin,:stop))
define_behavior {
process(clk,rst) {
#async reset:
if rst == '1'
puts "RESET"
state_sig << :start
elsif clk.event && clk == '1'
case state_sig.inspect
when :start
state_sig << :wash
when :wash
state_sig << :rinse
when :rinse
state_sig << :spin
when :spin
state_sig << :stop
when :stop
#stay here till reset
else
raise "invalid state! #{state_sig.state}"
end
end
}
process(state_sig) {
#prints message whenever state_sig changes:
puts "Current state is: #{state_sig}"
}
}
end
end
if $0 == __FILE__
include RHDL
include TestBench
clk = ClkGen.generator('0',2,2)
rst = Signal(Bit.new('1'))
fsm = WashMachine.new(clk,rst)
puts "step: 0"
step
puts "step: 1"
step
puts "step: 2"
step
rst << '0'
18.times do |i|
puts "step: #{i+2}"
step
end
rst << '1'
4.times do |i|
puts "step: #{i+2+18}"
step
rst << '0'
end
end
#end of washmachine
Whenever the signal StateSig changes it causes the second process to be
started. In this case the second process just prints the value of StateSig,
but you could also do more with it. Oh, the first process is triggered
whenever either clk or rst change. The arguments to 'process' are
considered it's sensitivity list (in HDL parlance) meaning that the process
is sensitive to any changes in the signals listed. You'll notice the
ClkGen declaration down towards the end, that sets up the clk signal
that toggles between 0 and 1 every 2 'cycles' - it's a 'free running'
clock. This causes the first process to be triggered everyh time the
clk signal changes. The rst signal is used to put the state machine into
it's initial state.
While RHDL is intended as an example of a hardware description language and
simulator for said language and thus for hardware design, I think it also
has potential for data-flow programming.
Phil