Last active
August 29, 2015 14:17
-
-
Save sawaken/17b29d979c683f7428c0 to your computer and use it in GitHub Desktop.
A library to do FRP in Ruby (no more used but working correctly.) Alternative library, Frypan is now released.
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 '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 |
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
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