Skip to content

Instantly share code, notes, and snippets.

@reu
Created July 12, 2023 17:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reu/f965a6d30c455192082dcf1d0a0daab2 to your computer and use it in GitHub Desktop.
Save reu/f965a6d30c455192082dcf1d0a0daab2 to your computer and use it in GitHub Desktop.
Ruby reactive signal implementation
require "set"
class Sig
def initialize(&computation)
@observers = Set.new
@dependencies = Set.new
@computation = computation || proc { nil }
compute if block_given?
end
def value
if caller
@observers << caller
caller.dependencies << self
end
@value
end
def update(&computation)
@computation = computation
compute
@value
end
def depends_on?(other_sig)
@dependencies.include? other_sig
end
def observed_by?(other_sig)
@observers.include? other_sig
end
protected
attr_reader :observers, :dependencies
def compute
@dependencies.each { |dependencies| dependencies.observers.delete(self) }
@dependencies = Set.new
new_value = storing_caller { @computation.call }
if new_value != @value
@value = new_value
observers = @observers
@observers = Set.new
observers.each { |observer| observer.compute }
end
end
private
def caller
Thread.current[thread_var]
end
def storing_caller
Thread.current[thread_var] = self
result = yield
ensure
Thread.current[thread_var] = nil
result
end
def thread_var
:signal
end
end
require "minitest/autorun"
class TestSig < MiniTest::Unit::TestCase
def test_dependent_updates
a = Sig.new { 1 }
b = Sig.new { 2 }
c = Sig.new { a.value + b.value }
assert_equal 3, c.value
a.update { 2 }
assert_equal 4, c.value
end
def test_properly_clear_dependencies
a = Sig.new { 1 }
b = Sig.new { 2 }
c = Sig.new { a.value + b.value }
assert c.depends_on?(a)
assert c.depends_on?(b)
assert a.observed_by?(c)
assert b.observed_by?(c)
c.update { b.value + 1 }
assert !c.depends_on?(a)
assert c.depends_on?(b)
assert !a.observed_by?(c)
assert b.observed_by?(c)
end
def test_track_dynamic_dependencies
a = Sig.new { "alice" }
b = Sig.new { "bob" }
c = Sig.new { true }
d = Sig.new { c.value ? a.value : b.value }
assert_equal d.value, "alice"
c.update { false }
assert_equal d.value, "bob"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment