What would it take to change the behaviour of variable assignment?

D

Daniel 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 =3D DataFlow.new

bar =3D Thread.new(foo) {|fooinarr| print fooinarr[0]}
baz =3D Thread.new(foo) {|fooinarr| sleep(3);fooinarr[0] =3D "Hello World,
sorry I'm late"}

Or, alternatively

foo =3D DataFlow.new
Thread.new(foo) {|fooinarr| begin(fooinarr[0]} #Starts a time
consuming, but autonomous job
#Lots of user interaction
foo =3D 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.
 
A

Ara.T.Howard

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
===============================================================================
 
D

Daniel Nugent

Ara: Bah, I knew that someone was going to get confused about that.=20
This should be a little less ambiguous:

foo =3D DataFlow.new

bar =3D Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
" + params[0]}
baz =3D Thread.new(foo, "Sleepy Thread") do |params|
print params[1] + ": Going to sleep."
sleep(3);
params[0] =3D "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?
 
D

Daniel Nugent

Bah, that example wasn't good either....


foo =3D DataFlow.new

bar =3D Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
" + params[0]}

sleep(3)

baz =3D Thread.new(foo, "Sleepy Thread") do |params|
print params[1] + ": Going to sleep."
sleep(3);
params[0] =3D "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"

Ara: Bah, I knew that someone was going to get confused about that.
This should be a little less ambiguous:

foo =3D DataFlow.new

bar =3D Thread.new(foo, "Monitor Thread") {|params| print params[1] + ":
" + params[0]}
baz =3D Thread.new(foo, "Sleepy Thread") do |params|
print params[1] + ": Going to sleep."
sleep(3);
params[0] =3D "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?

Maybe matju's gridflow does this already?

http://gridflow.ca/latest/doc/
 
E

Eric Hodel

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...
 
A

Ara.T.Howard

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! ;-)

-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
===============================================================================
 
D

Daniel Nugent

It was a good shot. The thing is that DataFlow is not a collection.=20
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.

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:

harp:~ > cat a.rb
require 'thread'

STDOUT.sync =3D true

counter =3D Thread::new{ 42.times{|i| puts i; sleep 1} }

foo =3D DataFlow::new
+ bar =3D Thread::new(foo, "Monitor Thread"){|df, label| puts "#{
label } : #{ df }"}
sleep 3

baz =3D Thread::new(foo, "Sleepy Thread") do |df, label|
puts "#{ label } : Going to sleep."
sleep 3
+ df =3D "Hello World, sorry I'm late"
 
A

Ara.T.Howard

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.

-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
===============================================================================
 
D

Daniel Nugent

It's not about the syntax, well, it is, sorta, but it's not about
what's the smallest syntax or whatever.

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.
 
P

Phil Tomson

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 =3D DataFlow.new

bar =3D Thread.new(foo) {|fooinarr| print fooinarr[0]}
baz =3D Thread.new(foo) {|fooinarr| sleep(3);fooinarr[0] =3D "Hello World,
sorry I'm late"}

Or, alternatively

foo =3D DataFlow.new
Thread.new(foo) {|fooinarr| begin(fooinarr[0]} #Starts a time
consuming, but autonomous job
#Lots of user interaction
foo =3D 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.


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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top