Hello again Rubyists,
Since my last correspondance on making Lazy evaluation and Data Flow
variables seamless in Ruby, there's been a breakthrough: I think I got
them all working right!
Code with contrived demos follows:
#Lazy Evaluation
#Dan Nugent and several persons from #ruby-lang (with special thanks
to interferon for showing me how to make data_flow not stupid)
#A class that generates lazy proxy objects.
···
#
#The crutch of lazy evaluation is that you don't do something UNTIL it's needed.
#Code generators work on this principle to create collections that
would otherwise
#be infinite series.
#
#This class doesn't let you create a code generator, but it does let you
#avoid evaluating expensive code blocks until you absolutely,
positively, need to
#kill every mot... errr, absolutely need to. Just like call by name evaluation!
class Lazy
#DESTROY ALL METHODS! DESTROY ALL INSTANCE METHODS!
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
#The optional parameter switch allows you to define whether you'd like
#the object to evaluate the block once and save its value or repeatedly call
#the block every time a method is called against the Lazy object
#
#This option is present in the other two lazy lambda generators
def initialize(switch = :one_eval, &block)
@code_block = block
@mode = switch
end
def method_missing(symbol, *args)
if @mode == :one_eval
@real_obj ||= @code_block.call
@real_obj.__send__(symbol, *args)
else
@code_block.call().__send__(symbol, *args)
end
end
end
#Use this function (functor?) to do lazy evaluation of a code block
def lazy(switch = :one_eval, &block)
Lazy.new(switch, &block)
end
#Use this one to create a data flow variable (or data flow code-block
or whatever)
#
#A warning: If the passed block is an expensive operation or if nil or
false are valid
#return values of your block, you should define a custom conditional.
The default
#conditional repeatedly calls the block to determine if it is a
defined value yet.
#This is a bad thing in the case of either an expensive block or a
return value of nil
#or false
#
#However, most of the time, you can just leave it be.
def data_flow(cond = nil, switch = :one_eval, &block )
cond ||= lambda {block.call}
lazy(switch) do
until cond.call
Thread.pass
end
block.call
end
end
##BEGIN CONTRIVED DEMO##
=begin
foo = data_flow{$dfv}
bar = Thread.new {print foo}
baz = Thread.new {sleep(3);$dfv = "Hello World, sorry I'm late"}
bar.join
baz.join
=end
##AN EVEN MORE CONTRIVED, COUNTER-INTUITIVE (BUT FUNNIER) DEMO##
#This isn't a very practical example, but it demonstrates a (silly) reason
#you may want to allow the block to be evaluated multiple times
=begin
season = data_flow(nil,:mult){$season}
elmer = Thread.new do
#Here, we see the nice thing about the data flow variables.
#These threads aren't hogging resources in idle loops
#because they keep passing the buck back to the scheduler
#until there's something to do.
until season.match("FIRE")
print season + "\n"
$season = nil
end
print season + "\n"
print "BLAM!"
end
bugsanddaffy = Thread.new do
3.times do
$season = "Bugs: Duck Season!"
sleep(0.6)
$season = "Daffy: Wabbit Season!"
sleep(0.6)
end
$season = "Bugs: Wabbit Season!"
sleep(0.6)
$season = "Daffy: Duck Season!"
sleep(0.6)
$season = "Bugs: Wabbit Season!"
sleep(0.6)
$season = "Daffy: I say it's DUCK season, and I say..."
sleep(2.2)
$season = "FIRE!"
end
elmer.join
bugsanddaffy.join
=end
If you want to try my silly and contrived demos, be sure to get rid of
the =begin and =end. Otherwise, it should just run from the command
line.
--
-Dan Nugent