Class: Farce::Lazy

Inherits:
Abstract::Lazy show all
Includes:
Shareable::Tracked
Defined in:
lib/farce/lazy.rb

Overview

A ractor-shareable lazy value that is computed on demand. The factory runs once. Its result is transferred according to #mode. With the default :copy mode, each Ractor receives its own cached copy. The factory and its bound receiver must remain Ractor-shareable. Use Strict::Lazy for direct shareable results or Unshared::Lazy for local factories.

You can use it to make the following, common Ruby idiom ractor-safe:

class MyClass
  def expensive_attribute = @expensive_attribute ||= compute_expensive_attribute

  private

  def compute_expensive_attribute
    sleep 0.5 # pretend like we're working
    42
  end
end

# this will freeze the object without the expensive attribute being computed
object = Ractor.make_shareable(MyClass.new)

# this will fail
Ractor.new(object) { it.expensive_attribute }


object = MyClass.new

# this will work, but compute the expensive attribute five times, and also deep-copy any other
# unshareable data the object might hold
5.times { Ractor.new(object) { it.expensive_attribute } }

You can use Farce::Lazy to make expensive attributes ractor-safe:

class MyClass
  include Farce::Shareable

  def initialize
    @expensive_attribute = Farce::Lazy.new do
      sleep 0.5 # pretend like we're working
      42
    end
    super
  end

  def expensive_attribute = @expensive_attribute.value
end

object = MyClass.new

# Will only compute the expensive attribute once, and return the same value to all Ractors.
5.times { Ractor.new(object) { it.expensive_attribute } }

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods included from Abstract::Value

#blank?, #unwrap

Methods included from Internal::Copyable

#duplicable?

Constructor Details

#initialize(factory, mode: :copy) ⇒ Lazy #initialize(mode: :copy, self: nil) { ... } ⇒ Lazy

Valid modes are:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Overloads:

  • #initialize(factory, mode: :copy) ⇒ Lazy

    Parameters:

    • factory (Class, Proc, #call) —

      the shareable factory for the value

    • mode (Symbol) (defaults to: :copy) —

      the transfer mode for the computed result

  • #initialize(mode: :copy, self: nil) { ... } ⇒ Lazy

    Parameters:

    • mode (Symbol) (defaults to: :copy) —

      the transfer mode for the computed result

    • self (BasicObject) (defaults to: nil) —

      the shareable receiver to bind to the block

    Yields:

    • computes the value on first access

    Yield Returns:

    • (BasicObject) —

      the result to transfer



74
75
76
77
# File 'lib/farce/lazy.rb', line 74

def initialize(factory = nil, mode: :copy, **, &)
  @manager = marshal_mode_manager(mode)
  super(factory, **, &)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Farce::Abstract::Lazy

Instance Method Details

#freeze ⇒ self

Resolve the slot before freezing it. This does not freeze the result.

Returns:

  • (self)


89
90
91
92
93
# File 'lib/farce/lazy.rb', line 89

def freeze
  value
  internal_atom.freeze
  super
end

#mode ⇒ Symbol

Returns the transfer mode for the computed result.

Returns:

  • (Symbol) —

    the transfer mode for the computed result



80
# File 'lib/farce/lazy.rb', line 80

def mode = @manager.mode

#value ⇒ BasicObject

Compute once and return the result according to the configured transfer mode. Repeated reads return the same value within the current Ractor.

Returns:

  • (BasicObject) —

    the computed result



85
# File 'lib/farce/lazy.rb', line 85

def value = @manager.unwrap(super)