class Functional::Delay

Lazy evaluation of a block yielding an immutable result. Useful for expensive operations that may never be needed.

When a ‘Delay` is created its state is set to `pending`. The value and reason are both `nil`. The first time the `#value` method is called the enclosed opration will be run and the calling thread will block. Other threads attempting to call `#value` will block as well. Once the operation is complete the value will be set to the result of the operation or the reason will be set to the raised exception, as appropriate. All threads blocked on `#value` will return. Subsequent calls to `#value` will immediately return the cached value. The operation will only be run once. This means that any side effects created by the operation will only happen once as well.

@!macro thread_safe_immutable_object

@see clojuredocs.org/clojure_core/clojure.core/delay Clojure delay

Public Class Methods

new(&block) click to toggle source

Create a new ‘Delay` in the `:pending` state.

@yield the delayed operation to perform

@raise [ArgumentError] if no block is given

Calls superclass method
# File lib/functional/delay.rb, line 29
def initialize(&block)
  raise ArgumentError.new('no block given') unless block_given?
  super
  synchronize do
    @state = :pending
    @task  = block
  end
end

Public Instance Methods

fulfilled?() click to toggle source

Has the delay been fulfilled? @return [Boolean]

# File lib/functional/delay.rb, line 73
def fulfilled?
  synchronize{ @state == :fulfilled }
end
Also aliased as: value?
pending?() click to toggle source

Is delay completion still pending? @return [Boolean]

# File lib/functional/delay.rb, line 87
def pending?
  synchronize{ @state == :pending }
end
reason() click to toggle source

The exception raised when processing the block. Returns ‘nil` if the operation is still `:pending` or has been `:fulfilled`.

@return [StandardError] the exception raised when processing the block

else nil.
# File lib/functional/delay.rb, line 50
def reason
  synchronize{ @reason }
end
reason?()
Alias for: rejected?
rejected?() click to toggle source

Has the delay been rejected? @return [Boolean]

# File lib/functional/delay.rb, line 80
def rejected?
  synchronize{ @state == :rejected }
end
Also aliased as: reason?
state() click to toggle source

Current state of block processing.

@return [Symbol] the current state of block processing

# File lib/functional/delay.rb, line 41
def state
  synchronize{ @state }
end
value() click to toggle source

Return the (possibly memoized) value of the delayed operation.

If the state is ‘:pending` then the calling thread will block while the operation is performed. All other threads simultaneously calling `#value` will block as well. Once the operation is complete (either `:fulfilled` or `:rejected`) all waiting threads will unblock and the new value will be returned.

If the state is not ‘:pending` when `#value` is called the (possibly memoized) value will be returned without blocking and without performing the operation again.

@return [Object] the (possibly memoized) result of the block operation

# File lib/functional/delay.rb, line 67
def value
  synchronize{ execute_task_once }
end
value?()
Alias for: fulfilled?

Protected Instance Methods

execute_task_once() click to toggle source

@!visibility private

Execute the enclosed task then cache and return the result if the current state is pending. Otherwise, return the cached result.

@return [Object] the result of the block operation

# File lib/functional/delay.rb, line 99
def execute_task_once
  if @state == :pending
    begin
      @value = @task.call
      @state = :fulfilled
    rescue => ex
      @reason = ex
      @state  = :rejected
    end
  end
  @value
end