From: Daniel Nugent Date: 2005-09-13T10:44:14+09:00 Subject: 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