Class: Farce::Mutable
Overview
A wrapper making generic Ruby objects shareable while preserving mutability.
It achieves this by keeping an immutable copy of the object, and performing an atomic update of this reference for mutating method calls.
This means the entire object is copied whenever it is being mutated.
You should therefore favor dedicated data structures whenever possible, like using a Map instead of wrapping a Hash in a Mutable, or a Vector instead of doing the same with an Array.
Non-mutating calls do not incur this cost.
Use a transaction view to stage mutations alongside changes to other participants. The original value changes only when the transaction commits.
Direct Known Subclasses
Class Method Summary collapse
-
.[](factory) ⇒ Class
Creates a subclass of Mutable that automatically generates values based on the given factory (usually a class), by invoking
newon it with any arguments being passed on. -
.deref(mutable) ⇒ BasicObject
Turns the given Mutable into a frozen copy of the underlying object.
Instance Method Summary collapse
-
#initialize(object) ⇒ Mutable
constructor
Creates a new mutable version of the given object.
-
#respond_to?(name, include_private = false) ⇒ Boolean
Marshal hook discovery must inspect the wrapper without delegating to its snapshot.
Methods included from Shareable
Constructor Details
#initialize(object) ⇒ Mutable
Creates a new mutable version of the given object. The object will be copied on initialization if it isn't frozen. A frozen version of the object must be Ractor-shareable.
103 104 105 106 |
# File 'lib/farce/mutable.rb', line 103 def initialize(object) @atom = Internal::Atom.new Mutator.prepare(object) super() end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing ⇒ BasicObject (private)
Delegates all methods to the frozen copy of the current value.
If it throws a FrozenError it reruns the method against an unfrozen duplicate of the value,
freezes it, and uses that as internal value.
141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/farce/mutable.rb', line 141 def method_missing(...) # rubocop:disable Style/MissingRespondToMissing @atom.value.__send__(...) rescue ::FrozenError result = nil @atom.update do |current| copy = current.dup result = copy.__send__(...) result = self if result.equal?(copy) Mutator.prepare(copy, in_place: true) end result end |
Class Method Details
.[](factory) ⇒ Class
Creates a subclass of Farce::Mutable that automatically generates values based on the given factory (usually a class),
by invoking new on it with any arguments being passed on.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/farce/mutable.rb', line 80 def self.[](factory) raise ::Farce::Ractor::IsolationError, "factory is not shareable" unless ::Farce::Ractor.shareable?(factory) klass = ::Class.new(self) klass.set_temporary_name("#{name}[#{factory.name}]") klass.instance_variable_set(:@value_factory, factory) klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1 def self.new(...) = super(value_factory.new(...)) def self.value_factory return @value_factory if defined?(@value_factory) && @value_factory superclass.value_factory end RUBY klass.singleton_class.class_eval "undef []", __FILE__, __LINE__ klass end |
.deref(mutable) ⇒ BasicObject
Turns the given Farce::Mutable into a frozen copy of the underlying object.
68 |
# File 'lib/farce/mutable.rb', line 68 def self.deref(mutable) = Mutable === mutable ? Mutator.deref(mutable) : mutable |
Instance Method Details
#respond_to?(name, include_private = false) ⇒ Boolean
Marshal hook discovery must inspect the wrapper without delegating to its snapshot.
115 116 117 118 119 |
# File 'lib/farce/mutable.rb', line 115 def respond_to?(name, include_private = false) # rubocop:disable Style/OptionalBooleanParameter return true if name == :marshal_dump || name == :transaction_wrapper return false if Internal.marshal_protocol_method?(name) method_missing(:respond_to?, name, include_private) end |