Skip to content

Instantly share code, notes, and snippets.

@qiukun
Forked from sawaken/test_tiny_frp.rb
Last active August 29, 2015 14:21
Show Gist options
  • Save qiukun/2934b6d9ed29854550e3 to your computer and use it in GitHub Desktop.
Save qiukun/2934b6d9ed29854550e3 to your computer and use it in GitHub Desktop.
require 'test/unit'
require './tiny_frp.rb'
module TinyFRP
module UnitTest
module Util
def lift(&proc)
TinyFRP::Lift.new(&proc)
end
def foldp(init_state, &proc)
TinyFRP::Foldp.new(init_state, &proc)
end
def lifta(&proc)
TinyFRP::Lifta.new(&proc)
end
end
class TinyFRPTest < Test::Unit::TestCase
include Util
def test_lift
v1, v2 = 0, 1
node = lift{|a, b| a + b} << lift{v1 = v1 + 1} + lift{v2 = v2 * 2}
assert_equal([[1+2], [2+4], [3+8], [4+16], [5+32]], TinyFRP.process(node, 5))
end
def test_foldp
node = lift{|a, b| a + b} << foldp(0){|acc| acc + 1} + foldp(1){|acc| acc * 2}
assert_equal([[1+2], [2+4], [3+8], [4+16], [5+32]], TinyFRP.process(node, 5))
end
def test_composite
node1 = foldp(0){|a| a + 1} >> lift{|a| a + 1}
node2 = foldp(0){|a| a + 1} >> lift{|a| a - 1}
node3 = lift{|a, b| a * b} << node1 + node2
assert_equal([[1 * 1 - 1], [2 * 2 - 1], [3 * 3 - 1]], TinyFRP.process(node3, 3))
end
def test_bundle
node = foldp(0){|a| a + 1} + foldp(1){|a| a + 1} + foldp(2){|a| a + 1}
assert_equal([[1, 2, 3], [2, 3, 4], [3, 4, 5]], TinyFRP.process(node, 3))
end
def test_split
node = foldp(0){|a| a + 1} >> lift{|a| a - 1} * lift{|a| a + 1}
assert_equal([[0, 2], [1, 3], [2, 4]], TinyFRP.process(node, 3))
end
def test_lifta
inp = lift{0} + lift{1}
node = lifta{|a, b| [a, b]} << inp
assert_equal([[0, 1], [0, 1], [0, 1]], TinyFRP.process(node, 3))
end
end
end
end
module TinyFRP
def self.process(node, n)
n.times.inject(res: [], last_memo: {}){|acc|
memo = node.call({}, acc[:last_memo])
{res: acc[:res] + [memo[node]], last_memo: memo}
}[:res]
end
def self.loop(node, &proc)
last_memo = {}
loop {
memo = node.call({}, last_memo)
proc.call(*memo[node])
last_memo = memo
}
end
class Node
def >>(node)
Composite.new(self, node)
end
def <<(node)
Composite.new(node, self)
end
def +(node)
case [self, node]
when [Bundle, Bundle]
Bundle.new(*(self.nodes + node.nodes))
when [Node, Bundle]
Bundle.new(*([self] + node.nodes))
when [Bundle, Node]
Bundle.new(*(self.nodes + [node]))
else
Bundle.new(self, node)
end
end
def *(node)
case [self, node]
when [Split, Split]
Split.new(*(self.nodes + node.nodes))
when [Node, Split]
Split.new(*([self] + node.nodes))
when [Split, Node]
Split.new(*(self.nodes + [node]))
else
Split.new(self, node)
end
end
def **(integer)
Split.new(*integer.times.map{self})
end
def call(memo, last_memo, *args)
if memo.has_key?(self)
memo
else
calc(memo, last_memo, *args)
end
end
end
class Lift < Node
def initialize(&proc)
@proc = proc
end
def argc
@argc ||= @proc.parameters.select{|t, n| t == :opt}.count
end
def calc(memo, last_memo, *args)
memo.merge(self => [@proc.call(*args)])
end
end
class Lifta < Lift
def calc(memo, last_memo, *args)
memo.merge(self => @proc.call(*args))
end
end
class Foldp < Node
def initialize(initial_state, &proc)
@initial_state, @proc = initial_state, proc
end
def argc
@argc ||= @proc.parameters.select{|t, n| t == :opt}.count - 1
end
def update(last_state, diff)
case diff
when Hash
last_state.keys.map{|k|
if diff.has_key?(k)
[k, diff[k]]
else
[k, last_state[k]]
end
}.to_h
else
diff
end
end
def call(memo, last_memo, *args)
last_state = last_memo.has_key?(self) ? last_memo[self].first : @initial_state
memo.merge(self => [update(last_state, @proc.call(last_state, *args))])
end
end
class Composite < Node
def initialize(pnode, cnode)
@pnode, @cnode = pnode, cnode
end
def argc
@pnode.argc
end
def calc(memo, last_memo, *args)
p_res = @pnode.call(memo, last_memo, *args)
n_res = @cnode.call(p_res, last_memo, *p_res[@pnode])
n_res.merge(self => n_res[@cnode])
end
end
class Bundle < Node
attr_reader :nodes
def initialize(*nodes)
@nodes = nodes
end
def argc
@argc ||= @nodes.inject(0){|acc, n| acc + n.argc}
end
def calc(memo, last_memo, *args)
res = @nodes.inject(memo: memo, rest_args: args){|acc, n|
{
memo: n.call(acc[:memo], last_memo, *acc[:rest_args].take(n.argc)),
rest_args: acc[:rest_args].drop(n.argc)
}
}[:memo]
res.merge(self => @nodes.inject([]){|acc, n| acc + res[n]})
end
end
class Split < Node
attr_reader :nodes
def initialize(*nodes)
@nodes = nodes
end
def argc
@argc ||= @nodes.first.argc
end
def calc(memo, last_memo, *args)
res = @nodes.inject(memo){|acc, n| n.call(acc, last_memo, *args)}
res.merge(self => @nodes.inject([]){|acc, n| acc + res[n]})
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment