1 # = lazy.rb -- Lazy evaluation in Ruby
5 # Copyright 2005-2006 MenTaLguY <mental@rydia.net>
7 # You may redistribute it and/or modify it under the same terms as Ruby.
12 # Raised when a demanded computation diverges (e.g. if it tries to directly
15 class DivergenceError < Exception
16 def initialize( message="Computation diverges" )
21 # Wraps an exception raised by a lazy computation.
23 # The reason we wrap such exceptions in LazyException is that they need to
24 # be distinguishable from similar exceptions which might normally be raised
25 # by whatever strict code we happen to be in at the time.
27 class LazyException < DivergenceError
28 # the original exception
31 def initialize( reason )
33 super( "Exception in lazy computation: #{ reason } (#{ reason.class })" )
34 set_backtrace( reason.backtrace.dup ) if reason
38 # A handle for a promised computation. They are transparent, so that in
39 # most cases, a promise can be used as a proxy for the computation's result
40 # object. The one exception is truth testing -- a promise will always look
41 # true to Ruby, even if the actual result object is nil or false.
43 # If you want to test the result for truth, get the unwrapped result object
47 alias __class__ class #:nodoc:
48 instance_methods.each { |m| undef_method m unless m =~ /^__/ }
50 def initialize( &computation ) #:nodoc:
51 @computation = computation
53 def __synchronize__ #:nodoc:
57 # create this once here, rather than creating a proc object for
59 DIVERGES = lambda { raise DivergenceError.new } #:nodoc:
60 def DIVERGES.inspect #:nodoc:
64 def __result__ #:nodoc:
67 raise LazyException.new( @exception ) if @exception
69 computation = @computation
70 @computation = DIVERGES # trap divergence due to over-eager recursion
73 @result = demand( computation.call( self ) )
75 rescue DivergenceError
77 rescue Exception => exception
79 @exception = exception
80 raise LazyException.new( @exception )
89 self.class.new(@computation.bind(object))
95 "#<#{ __class__ } computation=#{ @computation.inspect }>"
102 def respond_to?( message ) #:nodoc:
103 message = message.to_sym
104 message == :__result__ or
105 message == :inspect or
106 __result__.respond_to? message
109 def method_missing( *args, &block ) #:nodoc:
110 __result__.__send__( *args, &block )
118 # The promise() function is used together with demand() to implement
119 # lazy evaluation. It returns a promise to evaluate the provided
120 # block at a future time. Evaluation can be demanded and the block's
121 # result obtained via the demand() function.
123 # Implicit evaluation is also supported: the first message sent to it will
124 # demand evaluation, after which that message and any subsequent messages
125 # will be forwarded to the result object.
127 # As an aid to circular programming, the block will be passed a promise
128 # for its own result when it is evaluated. Be careful not to force
129 # that promise during the computation, lest the computation diverge.
131 def promise( &computation ) #:yields: result
132 Lazy::Promise.new &computation
135 # Forces the result of a promise to be computed (if necessary) and returns
136 # the bare result object. Once evaluated, the result of the promise will
137 # be cached. Nested promises will be evaluated together, until the first
138 # non-promise result.
140 # If called on a value that is not a promise, it will simply return it.
142 def demand( promise )
143 if promise.respond_to? :__result__
145 else # not really a promise