raskell 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +72 -0
- data/ROADMAP.md +15 -0
- data/lib/raskell.rb +11 -0
- data/lib/raskell/array.rb +209 -0
- data/lib/raskell/f.rb +1448 -0
- data/lib/raskell/folds.rb +188 -0
- data/lib/raskell/identity.rb +34 -0
- data/lib/raskell/integer.rb +23 -0
- data/lib/raskell/nothing.rb +5 -0
- data/lib/raskell/object.rb +27 -0
- data/lib/raskell/proc.rb +111 -0
- data/lib/raskell/streams.rb +440 -0
- data/lib/version.rb +3 -0
- metadata +58 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
|
4
|
+
class Foldl
|
5
|
+
|
6
|
+
alias_method :standard_ruby_kind_of?, :kind_of?
|
7
|
+
|
8
|
+
def kind_of?(clazz)
|
9
|
+
[Proc].include?(clazz) || standard_ruby_kind_of?(clazz)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*monoids) # must be [fn, unit] pair, a monoid
|
13
|
+
@monoids = monoids
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def monoids
|
18
|
+
@monoids
|
19
|
+
end
|
20
|
+
|
21
|
+
def *(lamb)
|
22
|
+
->(x) { self.( lamb.( x ) ) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def |(lamb)
|
26
|
+
->(x) { lamb.( self.( x ) ) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def <<(val)
|
30
|
+
self.(val.())
|
31
|
+
end
|
32
|
+
|
33
|
+
def >>(lamb)
|
34
|
+
lamb.(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def +(foldl)
|
38
|
+
if foldl.kind_of?(Foldl)
|
39
|
+
Foldl.new(*(self.monoids + foldl.monoids))
|
40
|
+
else
|
41
|
+
raise "Cannot add two non-folds together"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def call(stream)
|
46
|
+
if stream.respond_to?(:to_stream)
|
47
|
+
if @monoids.length > 1
|
48
|
+
fn = ->(acc, el) { F.zip_with.(F.apply_fn).(@monoids.map(&:first), acc, @monoids.map {|x| el }).to_a }
|
49
|
+
F.foldleft.(fn, @monoids.map(&:last)).(stream).to_a
|
50
|
+
else
|
51
|
+
F.foldleft.(*@monoids.first).(stream)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise "Cannot call Foldl on an object that does not have to_stream defined."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@@foldl = ->(f,u) { Foldl.new([f,u])}
|
59
|
+
def self.foldl
|
60
|
+
@@foldl
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class F
|
65
|
+
include Singleton
|
66
|
+
end
|
67
|
+
|
68
|
+
F.define_singleton_method(:foldl) { Foldl.foldl }
|
69
|
+
|
70
|
+
class Scanl
|
71
|
+
alias_method :standard_ruby_kind_of?, :kind_of?
|
72
|
+
|
73
|
+
def kind_of?(clazz)
|
74
|
+
[Proc].include?(clazz) || standard_ruby_kind_of?(clazz)
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(*monoids) # must be [fn, unit] pair, a monoid
|
78
|
+
@monoids = monoids
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def monoids
|
83
|
+
@monoids
|
84
|
+
end
|
85
|
+
|
86
|
+
def *(lamb)
|
87
|
+
->(x) { self.( lamb.( x ) ) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def |(lamb)
|
91
|
+
->(x) { lamb.( self.( x ) ) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def <<(val)
|
95
|
+
self.(val.())
|
96
|
+
end
|
97
|
+
|
98
|
+
def >>(lamb)
|
99
|
+
lamb.(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
def +(scanl)
|
103
|
+
if scanl.kind_of?(Scanl)
|
104
|
+
Scanl.new(*(self.monoids + scanl.monoids))
|
105
|
+
else
|
106
|
+
raise "Cannot add two non-folds together"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def call(stream)
|
111
|
+
if stream.respond_to?(:to_stream)
|
112
|
+
if @monoids.length > 1
|
113
|
+
fn = ->(acc, el) { F.zip_with.(F.apply_fn).(@monoids.map(&:first), acc, @monoids.map {|x| el }).to_a }
|
114
|
+
F.scanleft.(fn, @monoids.map(&:last)).(stream)
|
115
|
+
else
|
116
|
+
F.scanleft.(*@monoids.first).(stream)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
raise "Cannot call Foldl on an object that does not have to_stream defined."
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
@@scanl = ->(f,u) { Scanl.new([f,u])}
|
124
|
+
def self.scanl
|
125
|
+
@@scanl
|
126
|
+
end
|
127
|
+
end
|
128
|
+
F.define_singleton_method(:scanl) { Scanl.scanl }
|
129
|
+
|
130
|
+
|
131
|
+
class Mapl
|
132
|
+
alias_method :standard_ruby_kind_of?, :kind_of?
|
133
|
+
|
134
|
+
def kind_of?(clazz)
|
135
|
+
[Proc].include?(clazz) || standard_ruby_kind_of?(clazz)
|
136
|
+
end
|
137
|
+
|
138
|
+
def initialize(*functions)
|
139
|
+
@functions = functions
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def functions
|
144
|
+
@functions
|
145
|
+
end
|
146
|
+
|
147
|
+
def *(lamb)
|
148
|
+
->(x) { self.( lamb.( x ) ) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def |(lamb)
|
152
|
+
->(x) { lamb.( self.( x ) ) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def <<(val)
|
156
|
+
self.(val.())
|
157
|
+
end
|
158
|
+
|
159
|
+
def >>(lamb)
|
160
|
+
lamb.(self)
|
161
|
+
end
|
162
|
+
|
163
|
+
def +(mapl)
|
164
|
+
if mapl.kind_of?(Mapl)
|
165
|
+
Mapl.new(*(self.functions + mapl.functions))
|
166
|
+
else
|
167
|
+
raise "Cannot add two non-maps together"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def call(stream)
|
172
|
+
if stream.respond_to?(:to_stream)
|
173
|
+
if @functions.length > 1
|
174
|
+
F.mapleft.(@functions).(stream)
|
175
|
+
else
|
176
|
+
F.mapleft.(@functions.first).(stream)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise "Cannot call Mapl on an object that does not have to_stream defined."
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
@@map = ->(f) { Mapl.new(f)}
|
184
|
+
def self.map
|
185
|
+
@@map
|
186
|
+
end
|
187
|
+
end
|
188
|
+
F.define_singleton_method(:map) { Mapl.map }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Identity
|
2
|
+
|
3
|
+
alias_method :standard_kind_of?, :kind_of?
|
4
|
+
def kind_of?(clazz)
|
5
|
+
clazz == Proc || standard_kind_of?(clazz)
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(arg)
|
9
|
+
arg
|
10
|
+
end
|
11
|
+
|
12
|
+
def *(lamb)
|
13
|
+
lamb
|
14
|
+
end
|
15
|
+
|
16
|
+
def |(lamb)
|
17
|
+
lamb
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(lamb)
|
21
|
+
->(x) { x } + lamb
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(val)
|
25
|
+
# feed data from the right
|
26
|
+
self.(val.())
|
27
|
+
end
|
28
|
+
|
29
|
+
def >>(lamb)
|
30
|
+
# feed data from the left, assuming I am a wrapped Object of some sort
|
31
|
+
lamb.(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Integer
|
2
|
+
def self.empty
|
3
|
+
0
|
4
|
+
end
|
5
|
+
|
6
|
+
def foldl(func, unit)
|
7
|
+
i = 0
|
8
|
+
while i <= self
|
9
|
+
unit = func.(unit, i)
|
10
|
+
i+=1
|
11
|
+
end
|
12
|
+
unit
|
13
|
+
end
|
14
|
+
|
15
|
+
def foldr(func, unit)
|
16
|
+
i = self
|
17
|
+
while i >= 0
|
18
|
+
unit = func.(i, unit)
|
19
|
+
i-=1
|
20
|
+
end
|
21
|
+
unit
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
def self.empty
|
4
|
+
self.class.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def fmap(fn)
|
8
|
+
fn.(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def lift
|
12
|
+
->() { self }
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(*args)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply(fn)
|
20
|
+
fn.(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def deep_clone
|
24
|
+
self.respond_to?(:clone) && !self.kind_of?(Numeric) && !self.kind_of?(TrueClass) && !self.kind_of?(FalseClass) && !self.kind_of?(NilClass) ? self.clone : self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/raskell/proc.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
class Proc
|
3
|
+
alias_method :standard_ruby_call, :call
|
4
|
+
attr_accessor :is_tupled
|
5
|
+
|
6
|
+
def fmap(lamb)
|
7
|
+
self * lamb
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.pure(arg)
|
11
|
+
->(x) { arg }
|
12
|
+
end
|
13
|
+
|
14
|
+
def **(lamb) ## <*> from Control.Applicative
|
15
|
+
->(x) { self.(x, lamb.(x)) }
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
## <**> from Control.Applicative
|
20
|
+
def ^(lamb)
|
21
|
+
->(x) { lamb.(self.(x), x) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def bind(lamb)
|
25
|
+
->(x) { lamb.(self.(x), x)}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Just a friendly reminder
|
29
|
+
# .() is shorthand for .call()
|
30
|
+
# and self.arity is the number of arguments this Proc takes
|
31
|
+
|
32
|
+
def [](*args)
|
33
|
+
call(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(*args)
|
37
|
+
args_to_consume = args.take(self.arity < 0 ? self.arity.abs : self.arity) ## the < 1 here is because ruby stores *args as an arity of one more than the required args, in a negative number
|
38
|
+
remaining_args = args.drop(self.arity < 0 ? self.arity.abs : self.arity)
|
39
|
+
|
40
|
+
if !args_to_consume.respond_to?(:length) ## then we're dealing with a *args-only lambda, and we have enough args. just apply them all
|
41
|
+
result = self.standard_ruby_call(*args)
|
42
|
+
elsif self.arity == 0
|
43
|
+
result = self.standard_ruby_call()
|
44
|
+
elsif args.length == 0
|
45
|
+
#interpret application with no arguments on a non-zero-arity function as a no-op
|
46
|
+
return self
|
47
|
+
elsif (self.arity < 0 && args.length >= self.arity.abs - 1) ## then we're dealing with a *args-based lambda, and we have enough args. just apply them all
|
48
|
+
result = self.standard_ruby_call(*args)
|
49
|
+
elsif args_to_consume.length < self.arity
|
50
|
+
#if you have too few arguments, return a lambda asking for more before attempting to re-apply
|
51
|
+
return ->(x) { self.call( *( args + [x] ) ) }
|
52
|
+
elsif self.arity < 0
|
53
|
+
return ->(*xs) { self.call( *( args + xs.deep_clone ) ) }
|
54
|
+
else
|
55
|
+
#otherwise, apply the arguments
|
56
|
+
result = self.standard_ruby_call(*args_to_consume)
|
57
|
+
end
|
58
|
+
# if the result is a proc, make sure to unwrap further by recursively calling with any remaining arguments
|
59
|
+
(result.kind_of?(Proc) && remaining_args.length > 0) || (result.kind_of?(Proc) && remaining_args.length == 0 && result.respond_to?(:arity) && result.arity == 0) ? result.call(*remaining_args) : result
|
60
|
+
end
|
61
|
+
|
62
|
+
def *(lamb)
|
63
|
+
# You, then me
|
64
|
+
# like function composition
|
65
|
+
if lamb.class != Proc
|
66
|
+
lamb | self
|
67
|
+
else
|
68
|
+
->(x) { self.( lamb.( x ) ) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def |(lamb)
|
73
|
+
# Me, then you
|
74
|
+
# like unix pipes
|
75
|
+
if lamb.class != Proc
|
76
|
+
lamb * self
|
77
|
+
else
|
78
|
+
->(x) { lamb.( self.( x ) ) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def +(lamb)
|
83
|
+
## later need to check if they have the same arity, once I've fixed the arity function to handle nesting lambdas
|
84
|
+
this = self
|
85
|
+
|
86
|
+
result = ->(xs) {
|
87
|
+
if lamb.is_tupled && this.is_tupled
|
88
|
+
this.(xs) + lamb.(xs)
|
89
|
+
elsif lamb.is_tupled
|
90
|
+
[this.(xs)] + lamb.(xs)
|
91
|
+
elsif this.is_tupled
|
92
|
+
this.(xs) + [lamb.(xs)]
|
93
|
+
else
|
94
|
+
[this.(xs),lamb.(xs)]
|
95
|
+
end
|
96
|
+
}
|
97
|
+
result.is_tupled = true
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def <<(val)
|
102
|
+
# feed data from the right
|
103
|
+
self.(val.())
|
104
|
+
end
|
105
|
+
|
106
|
+
def >>(lamb)
|
107
|
+
# feed data from the left, assuming I am a wrapped Object of some sort
|
108
|
+
lamb.(self.())
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,440 @@
|
|
1
|
+
class Stream
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
def each(&fn)
|
5
|
+
item = self.next_item
|
6
|
+
while item != [:done]
|
7
|
+
fn.call(item[1]) if item.first == :yield
|
8
|
+
item = item.last.next_item
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :state, :next_item_function
|
14
|
+
def initialize(next_func, state, options={})
|
15
|
+
@next_item_function = next_func
|
16
|
+
@state = state
|
17
|
+
@options = options
|
18
|
+
## step_fn should return one of [:done], [:yield element stream], or [:skip stream]
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(stream)
|
23
|
+
if stream.respond_to?(:to_stream)
|
24
|
+
stream = stream.to_stream
|
25
|
+
next1 = self.another
|
26
|
+
next2 = stream.another
|
27
|
+
equal_so_far = next1 == next2 || (next1.first != :skip && next1[1] == next2[1])
|
28
|
+
while equal_so_far && !(next1 == [:done] || next2 == [:done])
|
29
|
+
next1 = next1.last.another
|
30
|
+
next2 = next2.last.another
|
31
|
+
equal_so_far = next1 == next2
|
32
|
+
end
|
33
|
+
equal_so_far
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ===(stream)
|
40
|
+
if stream.respond_to?(:to_stream)
|
41
|
+
stream = stream.to_stream
|
42
|
+
next1 = self.another
|
43
|
+
next2 = stream.another
|
44
|
+
equal_so_far = next1 == next2 || (next1.first != :skip && next1[1] === next2[1])
|
45
|
+
while equal_so_far && !(next1 == [:done] || next2 == [:done])
|
46
|
+
next1 = next1.last.another
|
47
|
+
next2 = next2.last.another
|
48
|
+
equal_so_far = next1 === next2
|
49
|
+
end
|
50
|
+
equal_so_far
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.to_stream(stream)
|
57
|
+
stream
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_stream
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def from_stream
|
65
|
+
FromStream.new().(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_a
|
69
|
+
from_stream
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_item
|
73
|
+
result = @next_item_function.(@state)
|
74
|
+
@state[2] = @state[1].clone.drop(@state[0]) if @options[:type] == "enumerator"
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def next_item_function
|
79
|
+
@next_item_function
|
80
|
+
end
|
81
|
+
|
82
|
+
def another
|
83
|
+
item = self.next_item
|
84
|
+
while item.first == :skip
|
85
|
+
item = item.last.next_item
|
86
|
+
end
|
87
|
+
item
|
88
|
+
end
|
89
|
+
|
90
|
+
def foldl(func, unit)
|
91
|
+
from_stream.foldl(func, unit)
|
92
|
+
end
|
93
|
+
|
94
|
+
def call(*args)
|
95
|
+
next_fn = ->(next_item) {
|
96
|
+
tag = next_item.first
|
97
|
+
fn = next_item[1]
|
98
|
+
stream = next_item.last
|
99
|
+
if tag == :done
|
100
|
+
[:done]
|
101
|
+
elsif tag == :skip
|
102
|
+
[:skip, Stream.new(next_fn, stream.state)]
|
103
|
+
elsif tag == :yield
|
104
|
+
[:yield, fn.(*args), Stream.new(next_fn, stream.state)]
|
105
|
+
else
|
106
|
+
raise "#{next_item} is a malformed stream result!"
|
107
|
+
end
|
108
|
+
} * self.next_item_function
|
109
|
+
Stream.new(next_fn, self.state)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
class FromStream
|
116
|
+
def initialize(clazz=Array, options={})
|
117
|
+
if clazz.kind_of?(Hash)
|
118
|
+
options = clazz
|
119
|
+
clazz = Array
|
120
|
+
end
|
121
|
+
@before_function = options['before']
|
122
|
+
@after_function = options['after']
|
123
|
+
@output_type = clazz
|
124
|
+
end
|
125
|
+
|
126
|
+
attr_accessor :before_function, :after_function
|
127
|
+
singleton_class.send(:alias_method, :standard_new, :new)
|
128
|
+
def self.new(clazz=Array,options={})
|
129
|
+
options['before'] && options['after'] ? ->(x) { self.standard_new(clazz,options).(x) } : self.standard_new(clazz,options)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
alias_method :standard_kind_of?, :kind_of?
|
136
|
+
def kind_of?(clazz)
|
137
|
+
clazz == Proc || standard_kind_of?(clazz)
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def call(stream)
|
142
|
+
before = @before_function
|
143
|
+
after = @after_function
|
144
|
+
stream = before.(stream) if before
|
145
|
+
result = self.unfold(stream)
|
146
|
+
after ? after.(result) : result
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def unfold(stream)
|
151
|
+
result = @output_type.new
|
152
|
+
# puts 'unfolding'
|
153
|
+
# puts stream.inspect
|
154
|
+
next_val = stream.next_item
|
155
|
+
while next_val.first != :done
|
156
|
+
#puts next_val.inspect
|
157
|
+
result << next_val[1] if next_val.first == :yield
|
158
|
+
next_val = next_val.last.next_item
|
159
|
+
end
|
160
|
+
result
|
161
|
+
end
|
162
|
+
|
163
|
+
def join(after)
|
164
|
+
before = self
|
165
|
+
if after.class == before.class && !after.before_function && !before.after_function
|
166
|
+
before.class.new({
|
167
|
+
'before' => before.before_function,
|
168
|
+
'after' => after.after_function
|
169
|
+
})
|
170
|
+
elsif ToStream == after.class
|
171
|
+
StreamTransducer.new({
|
172
|
+
'before' => before.before_function,
|
173
|
+
'inside' => (after.before_function || Identity.new) * ((after.kind_of?(StreamTransducer) ? inside_function : nil) || Identity.new) * (before.after_function || Identity.new),
|
174
|
+
'after' => after.after_function
|
175
|
+
})
|
176
|
+
elsif StreamTransducer == after.class && !before.after_function && !after.before_function
|
177
|
+
StreamTransducer.new({
|
178
|
+
'before' => before.before_function,
|
179
|
+
'inside' => after.inside_function,
|
180
|
+
'after' => after.after_function
|
181
|
+
})
|
182
|
+
else
|
183
|
+
->(xs) { after.(before.(xs)) }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def fuse(before_converter, after_converter)
|
188
|
+
before_converter.join(after_converter)
|
189
|
+
end
|
190
|
+
|
191
|
+
def *(lamb)
|
192
|
+
if lamb.kind_of?(Identity)
|
193
|
+
self
|
194
|
+
elsif [FromStream, StreamTransducer].include?(lamb.class)
|
195
|
+
## then fuse away the streams by just making this the Identity.new function
|
196
|
+
self.fuse(lamb, self)
|
197
|
+
else
|
198
|
+
self.class.new({ 'before' => (self.before_function || Identity.new) * lamb ,
|
199
|
+
'after' => self.after_function})
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def |(lamb)
|
204
|
+
if lamb.kind_of?(Identity)
|
205
|
+
self
|
206
|
+
elsif [FromStream, ToStream, StreamTransducer].include?(lamb.class)
|
207
|
+
## then fuse away the streams by just making this the Identity.new function
|
208
|
+
self.fuse(self, lamb)
|
209
|
+
else
|
210
|
+
self.class.new({ 'before' => self.before_function,
|
211
|
+
'after' => (self.after_function || Identity.new) | lamb })
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def <=(val)
|
216
|
+
# feed data from the right
|
217
|
+
self.(val.())
|
218
|
+
end
|
219
|
+
|
220
|
+
def >=(lamb)
|
221
|
+
# feed data from the left, assuming I am a wrapped Object of some sort
|
222
|
+
lamb.(self.())
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class ToStream
|
227
|
+
## Represents a generic to_stream function
|
228
|
+
def initialize(options={})
|
229
|
+
@before_function = options['before']
|
230
|
+
@after_function = options['after']
|
231
|
+
end
|
232
|
+
|
233
|
+
attr_reader :before_function, :after_function
|
234
|
+
singleton_class.send(:alias_method, :standard_new, :new)
|
235
|
+
def self.new(options={})
|
236
|
+
options['before'] && options['after'] ? ->(x) { self.standard_new(options).(x) } : self.standard_new(options)
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
alias_method :standard_kind_of?, :kind_of?
|
242
|
+
|
243
|
+
def kind_of?(clazz)
|
244
|
+
clazz == Proc || standard_kind_of?(clazz)
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def call(collection)
|
249
|
+
before = @before_function
|
250
|
+
after = @after_function
|
251
|
+
collection = before.(collection) if before
|
252
|
+
result = collection.to_stream
|
253
|
+
after ? after.(result) : result
|
254
|
+
end
|
255
|
+
|
256
|
+
def join(after)
|
257
|
+
## to = ToStream, from = FromStream
|
258
|
+
## to = ToStream, from = ToStream
|
259
|
+
## to = ToStream, from = StreamTransducer
|
260
|
+
before = self
|
261
|
+
if after.class == before.class && !after.before_function && !before.after_function
|
262
|
+
before.class.new({
|
263
|
+
'before' => before.before_function,
|
264
|
+
'after' => after.after_function
|
265
|
+
})
|
266
|
+
elsif FromStream == after.class
|
267
|
+
StreamTransducer.new({
|
268
|
+
'before' => before.before_function,
|
269
|
+
'inside' => (after.before_function || Identity.new) * ((after.kind_of?(StreamTransducer) ? inside_function : nil) || Identity.new) * (before.after_function || Identity.new),
|
270
|
+
'after' => after.after_function
|
271
|
+
})
|
272
|
+
elsif StreamTransducer == after.class && !before.after_function && !after.before_function
|
273
|
+
StreamTransducer.new({
|
274
|
+
'before' => before.before_function,
|
275
|
+
'inside' => after.inside_function,
|
276
|
+
'after' => after.after_function
|
277
|
+
})
|
278
|
+
|
279
|
+
else
|
280
|
+
->(xs) { after.(before.(xs)) }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def fuse(before_converter, after_converter)
|
285
|
+
before_converter.join(after_converter)
|
286
|
+
end
|
287
|
+
|
288
|
+
def *(lamb)
|
289
|
+
if lamb.kind_of?(Identity)
|
290
|
+
self
|
291
|
+
elsif [FromStream, ToStream, StreamTransducer].include?(lamb.class)
|
292
|
+
## then fuse away the streams by just making this the Identity.new function
|
293
|
+
self.fuse(lamb, self)
|
294
|
+
else
|
295
|
+
self.class.new({ 'before' => (self.before_function || Identity.new) * lamb ,
|
296
|
+
'after' => self.after_function})
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def |(lamb)
|
301
|
+
if lamb.kind_of?(Identity)
|
302
|
+
self
|
303
|
+
elsif [FromStream, ToStream, StreamTransducer].include?(lamb.class)
|
304
|
+
## then fuse away the streams by just making this the Identity.new function
|
305
|
+
self.fuse(self, lamb)
|
306
|
+
else
|
307
|
+
self.class.new({ 'before' => self.before_function,
|
308
|
+
'after' => (self.after_function || Identity.new) | lamb })
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def <=(val)
|
313
|
+
# feed data from the right
|
314
|
+
self.(val.())
|
315
|
+
end
|
316
|
+
|
317
|
+
def >=(lamb)
|
318
|
+
# feed data from the left, assuming I am a wrapped Object of some sort
|
319
|
+
lamb.(self.())
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class StreamTransducer
|
324
|
+
|
325
|
+
|
326
|
+
def initialize(options={})
|
327
|
+
@before_function = options['before']
|
328
|
+
@after_function = options['after']
|
329
|
+
@inside_function = options['inside']
|
330
|
+
end
|
331
|
+
|
332
|
+
attr_accessor :before_function, :after_function, :inside_function
|
333
|
+
singleton_class.send(:alias_method, :standard_new, :new)
|
334
|
+
def self.new(options={})
|
335
|
+
if options['inside'] && options['inside'].class != Identity
|
336
|
+
options['before'] && options['after'] ? ->(x) { self.standard_new(options).(x) } : self.standard_new(options)
|
337
|
+
else
|
338
|
+
->(x) { self.standard_new(options).(x) }
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
|
344
|
+
def +(stream_transducer)
|
345
|
+
##TODO handle case where before function and after functions exist
|
346
|
+
if stream_transducer.kind_of?(StreamTransducer) && !stream_transducer.before_function && !stream_transducer.after_function && !self.before_function && !self.after_function
|
347
|
+
StreamTransducer.new({
|
348
|
+
'inside' => self.inside_function + stream_transducer.inside_function
|
349
|
+
})
|
350
|
+
else
|
351
|
+
raise "#{stream_transducer.class} should be of class StreamTransducer to be combined via + with another StreamTransducer"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
alias_method :standard_kind_of?, :kind_of?
|
357
|
+
def kind_of?(clazz)
|
358
|
+
clazz == Proc || standard_kind_of?(clazz)
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
def call(arg)
|
363
|
+
before = self.before_function || Identity.new
|
364
|
+
after = self.after_function || Identity.new
|
365
|
+
after <= (F.from_stream * ->(stream) {
|
366
|
+
next_fn = self.inside_function * stream.next_item_function
|
367
|
+
Stream.new(next_fn, stream)
|
368
|
+
} <= F.to_stream.(before.(arg)))
|
369
|
+
end
|
370
|
+
|
371
|
+
def join(after)
|
372
|
+
|
373
|
+
## to = StreamTransducer, from = FromStream
|
374
|
+
## to = StreamTransducer, from = ToStream
|
375
|
+
## to = StreamTransducer, from = StreamTransducer
|
376
|
+
before = self
|
377
|
+
if after.class == before.class && !after.before_function && !before.after_function
|
378
|
+
before.class.new({
|
379
|
+
'before' => before.before_function,
|
380
|
+
'inside' => after.inside_function * before.inside_function,
|
381
|
+
'after' => after.after_function
|
382
|
+
})
|
383
|
+
elsif [ToStream,FromStream].include?(after.class) && !after.before_function && !before.after_function ## TODO TOMORROW figure this otu
|
384
|
+
## if i am a transducer and have no after, and from has no before
|
385
|
+
## then I cleanly merge with from and make a new transducer
|
386
|
+
## if i have an after, then I produce a lambda?
|
387
|
+
## NEXT STEP is to make a buuunch of test cases for all of this transducer/from/to merge stuff
|
388
|
+
## and then keep implementing until they all pass
|
389
|
+
## then build
|
390
|
+
StreamTransducer.new({
|
391
|
+
'before' => before.before_function,
|
392
|
+
'inside' => before.inside_function,
|
393
|
+
'after' => after.after_function
|
394
|
+
})
|
395
|
+
else
|
396
|
+
->(xs) { after.(before.(xs)) }
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def fuse(before_converter, after_converter)
|
401
|
+
before_converter.join(after_converter)
|
402
|
+
end
|
403
|
+
|
404
|
+
def *(lamb)
|
405
|
+
if lamb.kind_of?(Identity)
|
406
|
+
self
|
407
|
+
elsif [ToStream, StreamTransducer].include?(lamb.class)
|
408
|
+
## then fuse away the streams by just making this the Identity.new function
|
409
|
+
self.fuse(lamb, self)
|
410
|
+
else
|
411
|
+
self.class.new({ 'before' => (self.before_function || Identity.new) * lamb ,
|
412
|
+
'inside' => self.inside_function,
|
413
|
+
'after' => self.after_function})
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def |(lamb)
|
418
|
+
if lamb.kind_of?(Identity)
|
419
|
+
self
|
420
|
+
elsif [FromStream, ToStream, StreamTransducer].include?(lamb.class)
|
421
|
+
## then fuse away the streams by just making this the Identity.new function
|
422
|
+
self.fuse(self, lamb)
|
423
|
+
else
|
424
|
+
self.class.new({ 'before' => self.before_function,
|
425
|
+
'inside' => self.inside_function,
|
426
|
+
'after' => (self.after_function || Identity.new) | lamb })
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def <=(val)
|
431
|
+
# feed data from the right
|
432
|
+
self.(val.())
|
433
|
+
end
|
434
|
+
|
435
|
+
def >=(lamb)
|
436
|
+
# feed data from the left, assuming I am a wrapped Object of some sort
|
437
|
+
lamb.(self.())
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|