Skip to content

Instantly share code, notes, and snippets.

@reu
Created October 23, 2014 18:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reu/52d0cb910d774d226938 to your computer and use it in GitHub Desktop.
Save reu/52d0cb910d774d226938 to your computer and use it in GitHub Desktop.
# This class implements async evaluation by transparently executing the block on a different thread.
class Future < Lazy
def initialize(&block)
@attribute = ::Lazy.new(&block)
@thread = ::Thread.new { @attribute.__send__(:__call__) }
end
private
def __call__
@thread.join
@attribute
end
end
module Kernel
def future(&block)
Future.new(&block)
end
end
# This class implements lazy evaluation by transparently defer the execution of a block.
# The evaluation of the given block occurs if and when its result is first needed.
class Lazy < BasicObject
# As BasicObject does implement some methods, we must remove all of then to have a real
# proxy object. We just don't remove the methods whose starts and end with underlines,
# such as `__send__`.
instance_methods.each { |method| undef_method method unless method =~ /^(__.+__)$/ }
def initialize(&block)
@mutex = ::Mutex.new
@block = block
@called = false
end
# Here the magic happens, every method called on this object will be forwarded to the
# given block and then executed.
def method_missing(name, *args, &block)
__call__.__send__(name, *args, &block)
end
private
# This is where the block is evaluated.
def __call__
# As multiple threads may execute the block, we need a mutex here to guarantee the thread safeness.
@mutex.synchronize do
# If the block was already called, we don't need to do anything
unless @called
begin
@result = @block.call
@called = true
rescue ::Exception => error # rubocop:disable Lint/RescueException
# We must store the possible error that the block could raise to re-raise it on the correct
# context
@error = error
end
end
end unless @called
# If the block raised an error, we should re-raise it here, otherwise, just return the block result.
@error ? ::Kernel.raise(@error) : @result
end
end
module Kernel
def lazy(&block)
Lazy.new(&block)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment