Okay, we looked at Java, which has futures built-in in 1.5, though rather non-transparent futures, compared to something like the referenced Alice. Took some time last night, and today during some breaks in some training (ah, companies which do in-house training are great things) I beat on transparent futures for Ruby. I'm not totally happy with how I detect access for lazy futures, but it does work, if with a bit of magic:
module Futures
class Future
def initialize(func, *args)
@func = func
@args = args
yield self
end
def evaluate
@result = @func.call(self, *@args)
@result.methods.each do |m|
s = <<-EOS
def #{m}(*foos)
@result.send('#{m}', *foos)
end
EOS
instance_eval(s) unless ['instance_eval','__id__','__send__'].member? m
end
self
end
def method_missing(symbol, args)
evaluate unless available?
@result.send(symbol, args)
end
def available?
defined? @result
end
end
LAZY_SKIP_METHODS = ['instance_eval', '__id__', '__send__',
'evaluate', 'available?', '==', '===',
'=~', 'method_missing']
def lazy
Future.new(proc { yield }) do |it|
it.methods.each do |m|
s = <<-EOS
alias __Futures_#{m} #{m}
def #{m}(*foos)
evaluate unless available?
__Futures_#{m}(*foos)
end
EOS
it.instance_eval(s) unless LAZY_SKIP_METHODS.member? m
end
end
end
SPAWN_SKIP_METHODS = ['instance_eval', '__f_thread=', '__id__',
'__send__', 'evaluate', 'available?',
'==', '===', '=~', 'method_missing']
def spawn
f = Future.new(proc { yield }) do |it|
def it.__f_thread=(t)
@thread = t
end
it.methods.each do |m|
s = <<-EOS
alias __Futures_#{m} #{m}
def #{m}(*foos)
@thread.join unless available?
__Futures_#{m}(*foos)
end
EOS
it.instance_eval(s) unless SPAWN_SKIP_METHODS.member? m
end
end
Thread.start do
f.__f_thread=Thread.current
f.evaluate
end
return f
end
end
Note the two Future generating methods, spawn
and lazy
. Spawn will asynchronously evaluate the block, ~replacing the Future with the result upon completion, and blocking until completion if anything forces evaluation. Lazy will evaluate and replace the future when somethign forces evaluation. Both take a block from which the return value will ~replace the Future upon evaluation. Here's how they're used:
Lazy:
require 'futures.rb'
include Futures
class Wombat
def initialize
puts "initializing a Wombat"
end
def say(x)
# wombats sigh a lot
puts "ahhhh #{x}"
end
end
thing = lazy { Wombat.new }
puts "here"
thing.say "nice"
Will produce the output:
here
initializing a Wombat
ahhhh nice
And the async usage is like:
class SlowWombat < Wombat
def initialize
puts "I am so slow..."
sleep 2
puts "Ah, there I go..."
end
end
other = spawn { SlowWombat.new }
puts "doing something while wombat wakes up..."
puts "gee, this is a slow wombat..."
other.say "uh huh"
Producing:
doing something while wombat wakes up...
I am so slow...
gee, this is a slow wombat...
Ah, there I go...
ahhhh uh huh
With a two second delay (had to run it a few times to get them nicely interspersed though ;-)
In both these examples I have the block instantiate a new object, but there is no need for that, whatever the block returns will ~replace the future when it is evaluated, so remote calls, etc, would be good candidates for async -- if you need the result before it gets back, you'll block on it when you try top get access it.
I have been saying ~replacing because the result of the evaluation isn't really replacing the future -- the future is delegating all calls to result when it becomes available, and doing other things before it becomes available (forcing lazy evaluation, waiting for the evaluation thread to finish). The method redefinition code was amazing fun to write. Maybe I really should learn Lisp macros...
Debugging this kind of absurdly dynamic code is fun, particularly when I forgot to exclude instance_eval
from the forwarded methods and couldn't figure out why some methods were just not getting defined on the Future
while others were. Oops.
All in all, GREAT fun, and a tool I am going to keep in my belt ;-)