lab42_streams 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +9 -1
- data/lib/lab42/stream/auto_import.rb +11 -0
- data/lib/lab42/stream/class_methods.rb +17 -0
- data/lib/lab42/stream/empty/enumerable.rb +0 -0
- data/lib/lab42/stream/empty.rb +33 -11
- data/lib/lab42/stream/enumerable.rb +273 -0
- data/lib/lab42/stream/higher_order.rb +33 -0
- data/lib/lab42/stream/kernel/extensions.rb +7 -0
- data/lib/lab42/stream/kernel.rb +9 -12
- data/lib/lab42/stream/proc.rb +25 -0
- data/lib/lab42/stream/utility.rb +27 -0
- data/lib/lab42/stream/version.rb +1 -1
- data/lib/lab42/stream.rb +36 -89
- metadata +73 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 932f01cab1874c1a2f68148df75733d5ab33561b
|
4
|
+
data.tar.gz: 9ec01ace503ab4739c4070cfb9eaacf8ce399f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8e99130eac0f83c13737636ce55024f13d41e11698ee62370c493f9393b7a6ec78b3d9b047419ffc0b57320c5dfaa36c678b1ce69dcc85a1e815ba9c9c550a5
|
7
|
+
data.tar.gz: fd06621f258ec2d9c1f95b7ec1d8cac37c25d18f2b22d21a9e441dee6831efae36624b26432d8841697185f56ebc7a5712fb205ea91c9e292c5ed1f29257d1e6
|
data/README.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
1
|
# lab42\_streams
|
2
2
|
|
3
|
-
Bringing Streams to
|
3
|
+
Bringing Streams to Ruby
|
4
|
+
|
5
|
+
An excellent introduction into `Streams` can be found [here](http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/6a-streams-part-1/)
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
Please see the [QED](http://rubyworks.github.io/qed/) demos [here](https://github.com/RobertDober/lab42_streams/blob/master/demo)
|
10
|
+
|
11
|
+
for details.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Lab42
|
2
|
+
class Stream
|
3
|
+
module ClassMethods
|
4
|
+
def iterate arg, beh=nil, &blk
|
5
|
+
beh = blk.make_behavior beh
|
6
|
+
__iterate__ arg, beh
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def __iterate__ arg, beh
|
11
|
+
cons_stream arg do
|
12
|
+
__iterate__ beh.( arg ), beh
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end # module ClassMethods
|
16
|
+
end # class Stream
|
17
|
+
end # module Lab42
|
File without changes
|
data/lib/lab42/stream/empty.rb
CHANGED
@@ -1,7 +1,24 @@
|
|
1
|
+
require 'forwarder'
|
2
|
+
|
3
|
+
require_relative 'empty/enumerable'
|
4
|
+
|
1
5
|
module Lab42
|
2
6
|
class Stream
|
3
7
|
class Empty < Stream
|
4
|
-
|
8
|
+
extend Forwarder
|
9
|
+
# It is the nature of the EmptyStream instance to return itself for a plethora of methods
|
10
|
+
# this can be expressed as follows:
|
11
|
+
forward_all :combine, :__combine__,
|
12
|
+
:drop, :drop_unitl, :drop_while,
|
13
|
+
:flatmap, :__flatmap__, :filter, :__filter__,
|
14
|
+
:inject_stream, :__inject__,
|
15
|
+
:lazy_take, :lazy_take_until, :lazy_take_while,
|
16
|
+
:make_cyclic, :map, :__map__,
|
17
|
+
:reduce,
|
18
|
+
:segment, :__segment__, :__scan__, :split_by, :split_by_value,
|
19
|
+
:zip, :__zip__,
|
20
|
+
to_object: :self, as: :itself
|
21
|
+
|
5
22
|
def append other
|
6
23
|
raise ArgumentError, "not a stream #{other}" unless self.class.superclass === other
|
7
24
|
# ??? Is the to_stream message a good idea
|
@@ -11,23 +28,27 @@ module Lab42
|
|
11
28
|
|
12
29
|
def empty?; true end
|
13
30
|
|
14
|
-
def filter *args, &blk; self end
|
15
31
|
|
16
32
|
def head; raise StopIteration, "head called on empty stream" end
|
33
|
+
def tail; raise StopIteration, "tail called on empty stream" end
|
34
|
+
|
17
35
|
|
18
|
-
def
|
19
|
-
|
20
|
-
# I believe that this definition is sound, although it is an obvious pitfall
|
21
|
-
# But falling into it once, means understanding streams better, well that is
|
22
|
-
# my opinion now, we will see what promises the future will bring...
|
23
|
-
def tail; self end
|
36
|
+
def inject *args; args.first end
|
37
|
+
alias_method :__inject__, :inject
|
24
38
|
|
39
|
+
# TODO: Move this into lab42/core as Object#itself
|
40
|
+
def itself *args, &blk; self end
|
41
|
+
|
42
|
+
def scan initial, *args, &blk
|
43
|
+
[initial]
|
44
|
+
end
|
45
|
+
|
46
|
+
def scan1 *args, &blk
|
47
|
+
[]
|
48
|
+
end
|
25
49
|
|
26
|
-
def flatmap *args, &blk; self end
|
27
|
-
def __flatmap__ a_proc; self end
|
28
50
|
|
29
51
|
private
|
30
|
-
def initialize; end
|
31
52
|
|
32
53
|
def self.new
|
33
54
|
@__instance__ ||= allocate
|
@@ -37,6 +58,7 @@ module Lab42
|
|
37
58
|
|
38
59
|
module ::Kernel
|
39
60
|
def empty_stream; Empty.new end
|
61
|
+
Lab42::Stream::EmptyStream = empty_stream
|
40
62
|
end # module ::Kernel
|
41
63
|
end # class Stream
|
42
64
|
end # module Lab42
|
@@ -1,3 +1,276 @@
|
|
1
1
|
module Enumerable
|
2
2
|
def to_stream; finite_stream self end
|
3
3
|
end
|
4
|
+
|
5
|
+
module Lab42
|
6
|
+
class Stream
|
7
|
+
IllegalState = Class.new RuntimeError
|
8
|
+
|
9
|
+
module Enumerable
|
10
|
+
|
11
|
+
def drop_until *bhv, &blk
|
12
|
+
bhv = blk.make_behavior( *bhv )
|
13
|
+
s = self
|
14
|
+
loop do
|
15
|
+
return s if bhv.(s.head)
|
16
|
+
s = s.tail
|
17
|
+
end
|
18
|
+
empty_stream
|
19
|
+
end
|
20
|
+
|
21
|
+
# N.B. Not implemented as drop_until( bhv.not )
|
22
|
+
# for performance reasons
|
23
|
+
def drop_while *bhv, &blk
|
24
|
+
bhv = blk.make_behavior( *bhv )
|
25
|
+
s = self
|
26
|
+
loop do
|
27
|
+
return s unless bhv.(s.head)
|
28
|
+
s = s.tail
|
29
|
+
end
|
30
|
+
empty_stream
|
31
|
+
end
|
32
|
+
|
33
|
+
def each
|
34
|
+
t = self
|
35
|
+
loop do
|
36
|
+
yield t.head
|
37
|
+
t = t.tail
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def each_without_loops
|
42
|
+
visited = {}
|
43
|
+
t = self
|
44
|
+
loop do
|
45
|
+
h = t.head
|
46
|
+
yield h
|
47
|
+
visited[ t.object_id ] = true
|
48
|
+
t = t.tail
|
49
|
+
return if visited[t.object_id]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def force_all cache={}
|
54
|
+
x = []
|
55
|
+
each_without_loops do | ele |
|
56
|
+
if self.class === ele
|
57
|
+
if ! cache[ele.object_id]
|
58
|
+
cache[ele.object_id] = true
|
59
|
+
x << ele.force_all( cache )
|
60
|
+
end
|
61
|
+
else
|
62
|
+
x << ele
|
63
|
+
end
|
64
|
+
end
|
65
|
+
x
|
66
|
+
end
|
67
|
+
|
68
|
+
def lazy_take n=1
|
69
|
+
raise ArgumentError, "need a non negative Fixnum" if !(Fixnum === n) || n < 0
|
70
|
+
__lazy_take__ n
|
71
|
+
end
|
72
|
+
|
73
|
+
def __lazy_take__ n
|
74
|
+
return empty_stream if n.zero?
|
75
|
+
cons_stream( head ){ tail.__lazy_take__ n.pred }
|
76
|
+
end
|
77
|
+
|
78
|
+
def lazy_take_until *bhv, &blk
|
79
|
+
bhv = blk.make_behavior( *bhv )
|
80
|
+
__lazy_take_until__ bhv
|
81
|
+
end
|
82
|
+
|
83
|
+
def __lazy_take_until__ bhv
|
84
|
+
return empty_stream if bhv.(head)
|
85
|
+
cons_stream( head ){
|
86
|
+
tail.__lazy_take_until__ bhv
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def lazy_take_while *bhv, &blk
|
91
|
+
bhv = blk.make_behavior( *bhv )
|
92
|
+
__lazy_take_while__ bhv
|
93
|
+
end
|
94
|
+
|
95
|
+
def __lazy_take_while__ bhv
|
96
|
+
return empty_stream unless bhv.(head)
|
97
|
+
cons_stream( head ){
|
98
|
+
tail.__lazy_take_while__ bhv
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def reduce red=nil, &reducer
|
103
|
+
red = reducer.make_behavior( red )
|
104
|
+
tail.__inject__ head, red
|
105
|
+
end
|
106
|
+
|
107
|
+
def inject agg, *red, &reducer
|
108
|
+
__inject__ agg, reducer.make_behavior( *red )
|
109
|
+
end
|
110
|
+
|
111
|
+
def filter *args, &blk
|
112
|
+
__filter__ blk.make_behavior( *args )
|
113
|
+
end
|
114
|
+
|
115
|
+
def reject *args, &blk
|
116
|
+
__filter__ blk.make_behavior( *args ).not
|
117
|
+
end
|
118
|
+
|
119
|
+
def flatmap *args, &blk
|
120
|
+
__flatmap__ blk.make_behavior( *args )
|
121
|
+
end
|
122
|
+
|
123
|
+
def __flatmap__ a_proc
|
124
|
+
# require 'pry'
|
125
|
+
# binding.pry
|
126
|
+
hh = a_proc.( head )
|
127
|
+
if hh.empty?
|
128
|
+
tail.__flatmap__ a_proc
|
129
|
+
else
|
130
|
+
cons_stream( hh.head ){ hh.tail + tail.__flatmap__( a_proc ) }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def flatmap_with_each *args, &blk
|
135
|
+
__flatmap_with_each__ blk.make_behavior( *args )
|
136
|
+
end
|
137
|
+
|
138
|
+
def __flatmap_with_each__ a_proc, rest_of_enum = []
|
139
|
+
# Process expanded values
|
140
|
+
return cons_stream( rest_of_enum.first ){ __flatmap_with_each__ a_proc, rest_of_enum.drop( 1 ) } unless
|
141
|
+
rest_of_enum.empty?
|
142
|
+
|
143
|
+
# Map a scalar value
|
144
|
+
hh = a_proc.( head )
|
145
|
+
return cons_stream( hh ){ tail.__flatmap_with_each__ a_proc } unless
|
146
|
+
hh.respond_to? :each
|
147
|
+
|
148
|
+
# Start a new expansion...
|
149
|
+
# ... consider an empty expansion
|
150
|
+
return tail.__flatmap__ a_proc if hh.empty?
|
151
|
+
# ... expand values
|
152
|
+
cons_stream( hh.first ){ tail.__flatmap_with_each__( a_proc, hh.drop( 1 ) ) }
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def scan initial, *args, &blk
|
157
|
+
cons_stream initial do
|
158
|
+
__scan__ initial, blk.make_behavior( *args )
|
159
|
+
end.tap{ |r|
|
160
|
+
# require 'pry'
|
161
|
+
# binding.pry
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def scan1 *args, &blk
|
166
|
+
tail.scan( head, *args, &blk )
|
167
|
+
end
|
168
|
+
|
169
|
+
def __scan__ initial, beh
|
170
|
+
h = beh.(initial, head)
|
171
|
+
cons_stream( h ){ tail.__scan__ h, beh }
|
172
|
+
end
|
173
|
+
|
174
|
+
def take_until *bhv, &blk
|
175
|
+
bhv = blk.make_behavior( *bhv )
|
176
|
+
x = []
|
177
|
+
each do | ele |
|
178
|
+
return x if bhv.( ele )
|
179
|
+
x << ele
|
180
|
+
end
|
181
|
+
x
|
182
|
+
end
|
183
|
+
def take_while *bhv, &blk
|
184
|
+
bhv = blk.make_behavior( *bhv )
|
185
|
+
x = []
|
186
|
+
each do | ele |
|
187
|
+
return x unless bhv.( ele )
|
188
|
+
x << ele
|
189
|
+
end
|
190
|
+
x
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_a
|
194
|
+
take_while :true
|
195
|
+
end
|
196
|
+
alias_method :entries, :to_a
|
197
|
+
|
198
|
+
def make_cyclic
|
199
|
+
cons_stream( head ){
|
200
|
+
tail.append( make_cyclic )
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
def map *args, &blk
|
205
|
+
# TODO: Get this check and a factory to create a proc for this into core/fn
|
206
|
+
raise ArgumentError, "use either a block or arguments" if args.empty? && !blk || !args.empty? && blk
|
207
|
+
__map__ blk.make_behavior( *args )
|
208
|
+
end
|
209
|
+
|
210
|
+
def __map__ prc
|
211
|
+
cons_stream( prc.(head) ){ tail.__map__ prc }
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def reduce_while cond, red=nil, &reducer
|
216
|
+
red ||= reducer
|
217
|
+
tail.__inject_while__ head, cond, red
|
218
|
+
end
|
219
|
+
|
220
|
+
def take n=1
|
221
|
+
raise ArgumentError, "need a non negative Fixnum" if !(Fixnum === n) || n < 0
|
222
|
+
x = []
|
223
|
+
each do | ele |
|
224
|
+
return x if n.zero?
|
225
|
+
n -= 1
|
226
|
+
x << ele
|
227
|
+
end
|
228
|
+
x
|
229
|
+
end
|
230
|
+
|
231
|
+
def zip *other_streamables
|
232
|
+
streams = other_streamables.map{ |s|
|
233
|
+
self.class === s ? s : s.to_stream
|
234
|
+
}
|
235
|
+
__zip__ streams
|
236
|
+
end
|
237
|
+
|
238
|
+
def zip_as_ary *other_streamables
|
239
|
+
zip( *other_streamables )
|
240
|
+
.map( &:entries )
|
241
|
+
end
|
242
|
+
|
243
|
+
def __zip__ streams
|
244
|
+
cons_stream( [head] + streams.map(:head) ){
|
245
|
+
tail.__zip__ streams.map(:tail)
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
def __filter__ a_proc
|
250
|
+
if a_proc.( head )
|
251
|
+
cons_stream( head ){ tail.__filter__ a_proc }
|
252
|
+
else
|
253
|
+
tail.__filter__ a_proc
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def __inject__ agg, a_proc
|
258
|
+
new_agg = a_proc.(agg, head)
|
259
|
+
tail.__inject__ new_agg, a_proc
|
260
|
+
end
|
261
|
+
|
262
|
+
def __inject_while__ ival, cond, red
|
263
|
+
raise ConstraintError unless cond.(ival)
|
264
|
+
s = self
|
265
|
+
loop do
|
266
|
+
new_val = red.(ival, s.head)
|
267
|
+
return ival unless cond.(new_val)
|
268
|
+
ival = new_val
|
269
|
+
s = s.tail
|
270
|
+
return ival if s.empty?
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end # module Enumerable
|
274
|
+
include Enumerable
|
275
|
+
end # class Stream
|
276
|
+
end # module Lab42
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Lab42
|
2
|
+
class Stream
|
3
|
+
module HigherOrder
|
4
|
+
def combine *streams_and_op, &operation
|
5
|
+
op = streams_and_op.pop unless self.class === streams_and_op.last
|
6
|
+
op = operation.make_behavior op
|
7
|
+
# TODO: Decide what to do if op.arity and streams_and_op.size.succ do not match????
|
8
|
+
__combine__( op, *streams_and_op )
|
9
|
+
end
|
10
|
+
|
11
|
+
def __combine__ op, *streams
|
12
|
+
# TODO: Decide if we can continue if one of the streams is empty iff op.arity < 0
|
13
|
+
# for now no!
|
14
|
+
return empty_stream if streams.any?( &:empty? )
|
15
|
+
values = streams.map( &:head )
|
16
|
+
new_head = op.(head, *values)
|
17
|
+
cons_stream( new_head ){
|
18
|
+
tail.__combine__( op, *streams.map( &:tail ) )
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def split_into n
|
23
|
+
indexed = with_index
|
24
|
+
n.times.map do | i |
|
25
|
+
indexed
|
26
|
+
.filter{ |e, idx| idx % n == i }
|
27
|
+
.map( :first )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # module HigherOrder
|
31
|
+
include HigherOrder
|
32
|
+
end # class Stream
|
33
|
+
end # module Lab42
|
data/lib/lab42/stream/kernel.rb
CHANGED
@@ -6,12 +6,8 @@ module Kernel
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
def combine_streams
|
10
|
-
|
11
|
-
op ||= operation
|
12
|
-
cons_stream op.(s1.head, s2.head) do
|
13
|
-
combine_streams( s1.tail, s2.tail, op)
|
14
|
-
end
|
9
|
+
def combine_streams stream, *streams_and_ops, &operation
|
10
|
+
stream.combine( *streams_and_ops, &operation )
|
15
11
|
end
|
16
12
|
|
17
13
|
def cons_stream head, &tail
|
@@ -24,7 +20,7 @@ module Kernel
|
|
24
20
|
|
25
21
|
def cyclic_stream *args
|
26
22
|
args = args.first if
|
27
|
-
|
23
|
+
args.size == 1 && Enumerable === args.first
|
28
24
|
|
29
25
|
finite_stream( args ).make_cyclic
|
30
26
|
end
|
@@ -50,16 +46,17 @@ module Kernel
|
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
53
|
-
def
|
49
|
+
def iterate *args, &blk
|
54
50
|
if blk
|
55
|
-
cons_stream(*args){
|
51
|
+
cons_stream(*args){ iterate( blk.(*args), &blk ) }
|
56
52
|
else
|
57
53
|
rest = args.drop 1
|
58
54
|
if Method === rest.first
|
59
|
-
cons_stream( args.first ){
|
55
|
+
cons_stream( args.first ){ iterate( rest.first.(*([args.first] + rest.drop(1))), *rest ) }
|
60
56
|
else
|
61
|
-
cons_stream( args.first ){
|
62
|
-
|
57
|
+
cons_stream( args.first ){ iterate( sendmsg(*rest).(args.first), *rest ) }
|
58
|
+
end
|
63
59
|
end
|
64
60
|
end
|
61
|
+
alias_method :stream_by, :iterate
|
65
62
|
end
|
data/lib/lab42/stream/proc.rb
CHANGED
@@ -11,4 +11,29 @@ class Proc
|
|
11
11
|
end
|
12
12
|
}
|
13
13
|
end
|
14
|
+
|
15
|
+
# TODO: Use this from core/fn as soon as available
|
16
|
+
def make_behavior *args
|
17
|
+
raise ArgumentError, "cannot specify behavior with block and args: #{args.inspect}" unless args.compact.empty?
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def not
|
22
|
+
-> (*args, &blk) {
|
23
|
+
! self.(*args, &blk)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class NilClass
|
29
|
+
# TODO: Use this from core/fn as soon as available
|
30
|
+
def make_behavior *args
|
31
|
+
return args.first if args.size == 1 && args.first.respond_to?( :call )
|
32
|
+
|
33
|
+
return ->(*a){
|
34
|
+
args.first(*(args.drop(1)+a))
|
35
|
+
} if args.first.respond_to?( :call )
|
36
|
+
|
37
|
+
sendmsg( *args )
|
38
|
+
end
|
14
39
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lab42
|
2
|
+
class Stream
|
3
|
+
module Utility
|
4
|
+
|
5
|
+
def segment *args, &blk
|
6
|
+
__segment__ blk.make_behavior( *args )
|
7
|
+
end
|
8
|
+
|
9
|
+
def __segment__ beh
|
10
|
+
if beh.( head )
|
11
|
+
cons_stream( cons_stream( head ){ tail.lazy_take_until beh } ){
|
12
|
+
tail.drop_until( beh ).__segment__ beh
|
13
|
+
}
|
14
|
+
else
|
15
|
+
cons_stream( lazy_take_until beh ){
|
16
|
+
tail.drop_until( beh ).__segment__ beh
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_index start=0
|
22
|
+
zip_as_ary iterate( start, :succ )
|
23
|
+
end
|
24
|
+
end # module Utility
|
25
|
+
include Utility
|
26
|
+
end # class Stream
|
27
|
+
end # module Lab42
|
data/lib/lab42/stream/version.rb
CHANGED
data/lib/lab42/stream.rb
CHANGED
@@ -1,16 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
|
2
|
+
require_relative './stream/empty'
|
3
|
+
require_relative './stream/delayed'
|
4
|
+
require_relative './stream/kernel'
|
5
|
+
require_relative './stream/array'
|
6
|
+
require_relative './stream/enumerable'
|
7
|
+
require_relative './stream/higher_order'
|
8
|
+
require_relative './stream/hash'
|
9
|
+
require_relative './stream/proc'
|
10
|
+
require_relative './stream/class_methods'
|
11
|
+
require_relative './stream/utility'
|
12
|
+
|
13
|
+
# TODO: This should rather be implemented in lab_42/core/fn
|
14
|
+
require_relative './stream/kernel/extensions'
|
8
15
|
|
9
16
|
module Lab42
|
10
17
|
class Stream
|
18
|
+
extend ClassMethods
|
19
|
+
|
11
20
|
ConstraintError = Class.new RuntimeError
|
12
|
-
include Enumerable
|
13
21
|
attr_reader :head, :promise
|
22
|
+
alias_method :first, :head
|
14
23
|
|
15
24
|
def append other
|
16
25
|
raise ArgumentError, "not a stream #{other}" unless self.class === other
|
@@ -18,6 +27,13 @@ module Lab42
|
|
18
27
|
end
|
19
28
|
alias_method :+, :append
|
20
29
|
|
30
|
+
|
31
|
+
def combine_streams *args, &operation
|
32
|
+
op = args.shift unless self.class === args.first
|
33
|
+
raise ArgumentError, "Missing stream parameters" if args.empty?
|
34
|
+
__combine_streams__ operation.make_behavior( op ), args
|
35
|
+
end
|
36
|
+
|
21
37
|
def drop n = 1
|
22
38
|
raise ArgumentError, "not a non negative number" if n < 0
|
23
39
|
t = self
|
@@ -28,102 +44,33 @@ module Lab42
|
|
28
44
|
end
|
29
45
|
end
|
30
46
|
|
31
|
-
def each
|
32
|
-
t = self
|
33
|
-
loop do
|
34
|
-
yield t.head
|
35
|
-
t = t.tail
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
47
|
def empty?; false end
|
40
48
|
|
41
|
-
def filter *args, &blk
|
42
|
-
# TODO: Get this check and a factory to create a proc for this into core
|
43
|
-
raise ArgumentError, "use either a block or arguments" if args.empty? && !blk || !args.empty? && blk
|
44
|
-
filter_by_proc mk_proc( blk || args )
|
45
|
-
end
|
46
|
-
|
47
|
-
def filter_by_proc prc
|
48
|
-
if prc.(head)
|
49
|
-
cons_stream(head){ tail.filter_by_proc prc }
|
50
|
-
else
|
51
|
-
# TODO: Replace this with Delayed Stream (1 off)
|
52
|
-
tail.filter_by_proc prc
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def flatmap *args, &blk
|
57
|
-
if args.empty?
|
58
|
-
__flatmap__ blk
|
59
|
-
elsif args.size == 1 && args.first.respond_to?( :call )
|
60
|
-
__flatmap__ args.first
|
61
|
-
else
|
62
|
-
__flatmap__ sendmsg(*args)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def __inject_while__ ival, cond, red
|
67
|
-
raise ConstraintError unless cond.(ival)
|
68
|
-
s = self
|
69
|
-
loop do
|
70
|
-
new_val = red.(ival, s.head)
|
71
|
-
return ival unless cond.(new_val)
|
72
|
-
ival = new_val
|
73
|
-
s = s.tail
|
74
|
-
return ival if s.empty?
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def make_cyclic
|
79
|
-
cons_stream( head ){
|
80
|
-
tail.append( make_cyclic )
|
81
|
-
}
|
82
|
-
end
|
83
|
-
|
84
|
-
def map *args, &blk
|
85
|
-
# TODO: Get this check and a factory to create a proc for this into core/fn
|
86
|
-
raise ArgumentError, "use either a block or arguments" if args.empty? && !blk || !args.empty? && blk
|
87
|
-
transform_by_proc mk_proc( blk || args )
|
88
|
-
end
|
89
|
-
|
90
|
-
def reduce_while cond, red=nil, &reducer
|
91
|
-
red ||= reducer
|
92
|
-
tail.__inject_while__ head, cond, red
|
93
|
-
end
|
94
|
-
|
95
49
|
def tail
|
96
50
|
promise.()
|
97
51
|
end
|
98
52
|
|
99
53
|
def to_stream; self end
|
100
54
|
|
101
|
-
def
|
102
|
-
|
103
|
-
end
|
55
|
+
def __combine_streams__ op, args
|
56
|
+
return empty_stream if args.any?(&sendmsg(:empty?))
|
104
57
|
|
105
|
-
|
106
|
-
|
107
|
-
if hh.empty?
|
108
|
-
tail.__flatmap__ a_proc
|
109
|
-
else
|
110
|
-
cons_stream( hh.head ){ hh.tail + tail.__flatmap__( a_proc ) }
|
111
|
-
end
|
58
|
+
new_head = op.(head, *args.map(&sendmsg(:head)))
|
59
|
+
cons_stream( new_head ){ tail.__combine_streams__(op, args.map(&sendmsg(:tail))) }
|
112
60
|
end
|
113
61
|
|
114
62
|
private
|
115
63
|
def initialize h, t=nil, &tail
|
116
|
-
|
117
|
-
|
64
|
+
@head = h
|
65
|
+
@promise = ( t || tail ).memoized
|
118
66
|
end
|
119
67
|
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
68
|
+
# def mk_proc args
|
69
|
+
# return args if Proc === args
|
70
|
+
# raise ArgumentError, "neither a Proc nor an array of args for Kernel#sendmsg" unless Array === args
|
71
|
+
# return args.first if Proc === args.first || Method === args.first
|
72
|
+
# sendmsg(*args)
|
73
|
+
# end
|
127
74
|
|
128
75
|
end # class Stream
|
129
76
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lab42_streams
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: forwarder2
|
@@ -52,37 +52,99 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.9.12
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-nav
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.2'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.
|
75
|
+
version: 2.14.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.14.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: qed
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ae
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.1'
|
62
118
|
type: :development
|
63
119
|
prerelease: false
|
64
120
|
version_requirements: !ruby/object:Gem::Requirement
|
65
121
|
requirements:
|
66
122
|
- - "~>"
|
67
123
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
124
|
+
version: '3.1'
|
69
125
|
description: Lazy Evaluation, Streams, Enumerator#Lazy
|
70
126
|
email: robert.dober@gmail.com
|
71
127
|
executables: []
|
72
128
|
extensions: []
|
73
129
|
extra_rdoc_files: []
|
74
130
|
files:
|
131
|
+
- LICENSE
|
132
|
+
- README.md
|
75
133
|
- lib/lab42/stream.rb
|
76
134
|
- lib/lab42/stream/array.rb
|
77
|
-
- lib/lab42/stream/
|
135
|
+
- lib/lab42/stream/auto_import.rb
|
136
|
+
- lib/lab42/stream/class_methods.rb
|
78
137
|
- lib/lab42/stream/delayed.rb
|
79
|
-
- lib/lab42/stream/hash.rb
|
80
138
|
- lib/lab42/stream/empty.rb
|
81
|
-
- lib/lab42/stream/
|
82
|
-
- lib/lab42/stream/kernel.rb
|
139
|
+
- lib/lab42/stream/empty/enumerable.rb
|
83
140
|
- lib/lab42/stream/enumerable.rb
|
84
|
-
-
|
85
|
-
-
|
141
|
+
- lib/lab42/stream/hash.rb
|
142
|
+
- lib/lab42/stream/higher_order.rb
|
143
|
+
- lib/lab42/stream/kernel.rb
|
144
|
+
- lib/lab42/stream/kernel/extensions.rb
|
145
|
+
- lib/lab42/stream/proc.rb
|
146
|
+
- lib/lab42/stream/utility.rb
|
147
|
+
- lib/lab42/stream/version.rb
|
86
148
|
homepage: https://github.com/RobertDober/lab42_streams
|
87
149
|
licenses:
|
88
150
|
- MIT
|
@@ -103,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
165
|
version: '0'
|
104
166
|
requirements: []
|
105
167
|
rubyforge_project:
|
106
|
-
rubygems_version: 2.
|
168
|
+
rubygems_version: 2.2.2
|
107
169
|
signing_key:
|
108
170
|
specification_version: 4
|
109
171
|
summary: Streams for Ruby 2.0
|