Lazy and Data Flow Variables REDUX!

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