Created
July 12, 2023 17:24
-
-
Save reu/f965a6d30c455192082dcf1d0a0daab2 to your computer and use it in GitHub Desktop.
Ruby reactive signal implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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