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