fr 0.9.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.
- data/README.md +0 -0
- data/Rakefile +81 -0
- data/lib/fr.rb +44 -0
- data/lib/fr/applicative.rb +0 -0
- data/lib/fr/array.rb +168 -0
- data/lib/fr/boolean.rb +15 -0
- data/lib/fr/either.rb +94 -0
- data/lib/fr/errors.rb +5 -0
- data/lib/fr/errors/zipper_error.rb +8 -0
- data/lib/fr/functor.rb +11 -0
- data/lib/fr/functor/array.rb +15 -0
- data/lib/fr/functor/either.rb +17 -0
- data/lib/fr/functor/maybe.rb +17 -0
- data/lib/fr/maybe.rb +76 -0
- data/lib/fr/monad.rb +236 -0
- data/lib/fr/monad/array.rb +21 -0
- data/lib/fr/monad/either.rb +18 -0
- data/lib/fr/monad/maybe.rb +24 -0
- data/lib/fr/monad/random.rb +50 -0
- data/lib/fr/monad/reader.rb +45 -0
- data/lib/fr/monad/state.rb +62 -0
- data/lib/fr/monad/writer.rb +49 -0
- data/lib/fr/monoid.rb +52 -0
- data/lib/fr/monoid/array.rb +13 -0
- data/lib/fr/monoid/boolean.rb +23 -0
- data/lib/fr/monoid/either.rb +13 -0
- data/lib/fr/monoid/maybe.rb +15 -0
- data/lib/fr/monoid/numeric.rb +31 -0
- data/lib/fr/monoid/string.rb +11 -0
- data/lib/fr/numeric.rb +1 -0
- data/lib/fr/object.rb +45 -0
- data/lib/fr/string.rb +1 -0
- data/lib/fr/szipper.rb +36 -0
- data/lib/fr/szipper/abstract_cursor.rb +63 -0
- data/lib/fr/szipper/edited_cursor.rb +62 -0
- data/lib/fr/szipper/enumerable.rb +266 -0
- data/lib/fr/szipper/head_cursor.rb +71 -0
- data/lib/fr/szipper/head_prev_cursor.rb +112 -0
- data/lib/fr/szipper/memoized_cursor.rb +59 -0
- data/lib/fr/szipper/node.rb +67 -0
- data/lib/fr/szipper/tail_next_cursor.rb +68 -0
- data/lib/fr/thunk.rb +73 -0
- data/lib/fr/tzipper.rb +38 -0
- data/lib/fr/tzipper/abstract_cursor.rb +351 -0
- data/lib/fr/tzipper/dangling_cursor.rb +107 -0
- data/lib/fr/tzipper/edited_cursor.rb +161 -0
- data/lib/fr/tzipper/memoized_cursor.rb +129 -0
- data/lib/fr/tzipper/node.rb +57 -0
- data/lib/fr/tzipper/path.rb +125 -0
- data/lib/fr/tzipper/root_cursor.rb +124 -0
- data/lib/fr/unfold.rb +93 -0
- data/spec/examples/array.example +0 -0
- data/spec/examples/monads/array.example +0 -0
- data/spec/examples/monads/maybe.example +0 -0
- data/spec/examples/monads/random.example +0 -0
- data/spec/examples/monads/state.example +0 -0
- data/spec/examples/szipper.example +652 -0
- data/spec/examples/thunk.example +0 -0
- data/spec/examples/tzipper.example +0 -0
- data/spec/examples/unfold.example +90 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/szipper/model.rb +225 -0
- data/spec/support/szipper/nodel.rb +144 -0
- metadata +116 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
class EditedCursor < AbstractCursor
|
5
|
+
attr_reader :node
|
6
|
+
|
7
|
+
def initialize(node, prev)
|
8
|
+
@node, @prev =
|
9
|
+
node, prev
|
10
|
+
end
|
11
|
+
|
12
|
+
def head?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def tail?
|
17
|
+
@node.tail?
|
18
|
+
end
|
19
|
+
|
20
|
+
def prev(phantom = false)
|
21
|
+
if @prev.head?
|
22
|
+
HeadCursor.new(@prev.node.copy(next: @node))
|
23
|
+
else
|
24
|
+
EditedCursor.new(@prev.node.copy(next: @node), @prev.prev)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def append(node)
|
29
|
+
EditedCursor.new(node.copy(next: @node.next), self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def prepend(node)
|
33
|
+
EditedCursor.new(node.copy(next: @node), prev)
|
34
|
+
end
|
35
|
+
|
36
|
+
def update(node = nil)
|
37
|
+
if node.nil?
|
38
|
+
EditedCursor.new(yield(@node).copy(next: @node.next), @prev)
|
39
|
+
else
|
40
|
+
EditedCursor.new(node.copy(next: @node.next), @prev)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def replace(node)
|
45
|
+
EditCursor.new(node, @prev)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete
|
49
|
+
if @prev.head?
|
50
|
+
HeadCursor.new(prev.node.copy(next: @node.next))
|
51
|
+
else
|
52
|
+
EditedCursor.new(prev.node.copy(next: @node.next), @prev.prev)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"Cursor([..., #{@node.inspect}, ...])"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
module Enumerable
|
5
|
+
|
6
|
+
def each
|
7
|
+
cursor = self
|
8
|
+
yield(cursor)
|
9
|
+
|
10
|
+
until cursor.tail?
|
11
|
+
cursor = cursor.next
|
12
|
+
yield(cursor)
|
13
|
+
end
|
14
|
+
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def all?
|
19
|
+
catch(:done) do
|
20
|
+
each{|c| throw(:done, false) if !yield(c.node) }; true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def any?
|
25
|
+
catch(:done) do
|
26
|
+
each{|c| throw(:done, true) if yield(c.node) }; false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def chunk
|
31
|
+
# todo: defined?
|
32
|
+
end
|
33
|
+
|
34
|
+
def collect
|
35
|
+
unless tail?
|
36
|
+
current = yield(node)
|
37
|
+
next_ = Fr.thunk { self.next.collect{|n| yield(n) }.node }
|
38
|
+
replace(current.copy(next: next_))
|
39
|
+
else
|
40
|
+
update(yield(node))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_concat
|
45
|
+
# todo: defined?
|
46
|
+
end
|
47
|
+
|
48
|
+
def count
|
49
|
+
foldl(0){|count, c| yield(c.node) ? count + 1 : count }
|
50
|
+
end
|
51
|
+
|
52
|
+
def cycle
|
53
|
+
# todo: defined?
|
54
|
+
end
|
55
|
+
|
56
|
+
def detect(default = nil)
|
57
|
+
catch(:done) do
|
58
|
+
each{|c| throw(:done, c) if yield(c.node) }; default
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def drop(n)
|
63
|
+
# Seek n nodes forward
|
64
|
+
nth = n.times.inject(self.next(true)){|c,_| c.next(true) }
|
65
|
+
mth = node.copy(next: nth.prev.node.next)
|
66
|
+
replace(mth)
|
67
|
+
end
|
68
|
+
|
69
|
+
def drop_while
|
70
|
+
# todo
|
71
|
+
end
|
72
|
+
|
73
|
+
def entries
|
74
|
+
foldl([]){|entries, c| entries << c.node }
|
75
|
+
end
|
76
|
+
|
77
|
+
alias_method :find, :detect
|
78
|
+
|
79
|
+
def filter
|
80
|
+
node = filter_{|n| yield(n) }
|
81
|
+
unless node.nil?
|
82
|
+
replace(node)
|
83
|
+
else
|
84
|
+
prev(true).truncate
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @private
|
89
|
+
def filter_
|
90
|
+
unless tail?
|
91
|
+
Fr.thunk do
|
92
|
+
if yield(node)
|
93
|
+
node.copy(next: self.next.filter_{|n| yield(n) })
|
94
|
+
else
|
95
|
+
self.next.filter_{|n| yield(n) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
else
|
99
|
+
yield(node) ?
|
100
|
+
node : nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :find_all, :filter
|
105
|
+
|
106
|
+
def index(default = nil)
|
107
|
+
position_ = position
|
108
|
+
|
109
|
+
catch(:done) do
|
110
|
+
each do |c|
|
111
|
+
if yield(c.node)
|
112
|
+
throw(:done, position_)
|
113
|
+
else
|
114
|
+
position_ += 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
default
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
alias_method :find_index, :index
|
123
|
+
|
124
|
+
# alias_method :first, :head
|
125
|
+
def first(n = 1)
|
126
|
+
if n == 1
|
127
|
+
head
|
128
|
+
else
|
129
|
+
# todo: defined?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def flat_map
|
134
|
+
# todo
|
135
|
+
end
|
136
|
+
|
137
|
+
def grep(pattern)
|
138
|
+
filter{|node| pattern === node }
|
139
|
+
end
|
140
|
+
|
141
|
+
def group_by
|
142
|
+
# todo: defined?
|
143
|
+
end
|
144
|
+
|
145
|
+
def include?(needle)
|
146
|
+
any?{|node| node == needle }
|
147
|
+
end
|
148
|
+
|
149
|
+
def foldl(svalue)
|
150
|
+
cursor = self
|
151
|
+
svalue = yield(svalue, cursor)
|
152
|
+
until cursor.tail?
|
153
|
+
cursor = cursor.next
|
154
|
+
svalue = yield(svalue, cursor)
|
155
|
+
end
|
156
|
+
svalue
|
157
|
+
end
|
158
|
+
|
159
|
+
alias_method :inject, :foldl
|
160
|
+
|
161
|
+
alias_method :map, :collect
|
162
|
+
|
163
|
+
def max
|
164
|
+
reduce{|a,b| a.node < b.node ? b : a }
|
165
|
+
end
|
166
|
+
|
167
|
+
def max_by
|
168
|
+
reduce{|a,b| yield(a.node) < yield(b.node) ? b : a }
|
169
|
+
end
|
170
|
+
|
171
|
+
def min
|
172
|
+
reduce{|a,b| a.node > b.node ? b : a }
|
173
|
+
end
|
174
|
+
|
175
|
+
def min_by
|
176
|
+
reduce{|a,b| yield(a.node) > yield(b.node) ? b : a }
|
177
|
+
end
|
178
|
+
|
179
|
+
def minmax
|
180
|
+
foldl([self, self]) do |(min,max), c|
|
181
|
+
[min.node > c.node ? c : min,
|
182
|
+
max.node < c.node ? c : max]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def minmax_by
|
187
|
+
foldl([self, self]) do |(min,max), c|
|
188
|
+
[yield(min.node) > yield(c.node) ? c : min,
|
189
|
+
yield(max.node) < yield(c.node) ? c : max]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def none?
|
194
|
+
catch(:done) do
|
195
|
+
each{|c| throw(:done, false) if yield(c.node) }; true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
alias_method :one?, :any?
|
200
|
+
|
201
|
+
def partition
|
202
|
+
# todo
|
203
|
+
end
|
204
|
+
|
205
|
+
def reduce(op = nil)
|
206
|
+
# [].reduce(op) #=> nil
|
207
|
+
# [a].reduce(op) #=> a
|
208
|
+
# [a,b].reduce(op) #=> a op b
|
209
|
+
# [a,b,c].reduce(op) #=> a op b op c
|
210
|
+
|
211
|
+
svalue = self
|
212
|
+
cursor = self
|
213
|
+
|
214
|
+
unless op.nil?
|
215
|
+
until cursor.tail?
|
216
|
+
cursor = cursor.next
|
217
|
+
svalue = svalue.send(op, cursor)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
until cursor.tail?
|
221
|
+
cursor = cursor.next
|
222
|
+
svalue = yield(svalue, cursor)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
svalue
|
227
|
+
end
|
228
|
+
|
229
|
+
# todo:
|
230
|
+
# - api: this always returns a cursor pointed at tail (ugh)
|
231
|
+
# - api: should be lazy
|
232
|
+
# - bug: doesn't filter tail element
|
233
|
+
def reject
|
234
|
+
cursor = self
|
235
|
+
until cursor.tail?
|
236
|
+
cursor = yield(cursor.node) ?
|
237
|
+
cursor.replace(cursor.node.next) :
|
238
|
+
cursor.next
|
239
|
+
end
|
240
|
+
cursor
|
241
|
+
end
|
242
|
+
|
243
|
+
alias_method :select, :filter
|
244
|
+
|
245
|
+
def sort
|
246
|
+
# todo: defined?
|
247
|
+
end
|
248
|
+
|
249
|
+
def take(n)
|
250
|
+
# todo: defined?
|
251
|
+
end
|
252
|
+
|
253
|
+
def take_while
|
254
|
+
# todo: defined?
|
255
|
+
end
|
256
|
+
|
257
|
+
alias_method :to_a, :entries
|
258
|
+
|
259
|
+
def zip(stream, *streams)
|
260
|
+
# todo: should be lazy
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
class HeadCursor < AbstractCursor
|
5
|
+
# @return [#next, #tail?]
|
6
|
+
attr_reader :node
|
7
|
+
|
8
|
+
def initialize(node)
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def position
|
13
|
+
0
|
14
|
+
end
|
15
|
+
|
16
|
+
def head?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def tail?
|
21
|
+
@node.tail?
|
22
|
+
end
|
23
|
+
|
24
|
+
def head
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def prev(phantom = false)
|
29
|
+
if phantom
|
30
|
+
HeadPrevCursor.new(@node)
|
31
|
+
else
|
32
|
+
raise Errors::ZipperError,
|
33
|
+
"head node has no prev"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def append(node)
|
38
|
+
EditedCursor.new(node.copy(next: @node.next), self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def prepend(node)
|
42
|
+
HeadCursor.new(node.copy(next: @node))
|
43
|
+
end
|
44
|
+
|
45
|
+
def update(node = nil)
|
46
|
+
if node.nil?
|
47
|
+
HeadCursor.new(yield(@node).copy(next: @node.next))
|
48
|
+
else
|
49
|
+
HeadCursor.new(node.copy(next: @node.next))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def replace(node)
|
54
|
+
HeadCursor.new(node)
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete
|
58
|
+
if tail?
|
59
|
+
HeadPrevCursor.new(nil)
|
60
|
+
else
|
61
|
+
HeadCursor.new(@node.next)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
"Cursor([#{@node.inspect}, ...])"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
class HeadPrevCursor < AbstractCursor
|
5
|
+
def initialize(node)
|
6
|
+
@node = node
|
7
|
+
end
|
8
|
+
|
9
|
+
def position
|
10
|
+
-1
|
11
|
+
end
|
12
|
+
|
13
|
+
def head?
|
14
|
+
@node.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def tail?
|
18
|
+
@node.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty?
|
22
|
+
@node.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def head
|
26
|
+
if @node.nil?
|
27
|
+
raise Errors::ZipperError,
|
28
|
+
"head.prev has no head"
|
29
|
+
else
|
30
|
+
HeadCursor.new(@node)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def tail
|
35
|
+
if @node.nil?
|
36
|
+
raise Errors::ZipperError,
|
37
|
+
"head.prev has no tail"
|
38
|
+
else
|
39
|
+
HeadCursor.new(@node).tail
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def prev(phantom = false)
|
44
|
+
if phantom
|
45
|
+
self
|
46
|
+
else
|
47
|
+
raise Errors::ZipperError,
|
48
|
+
"head.prev has no prev"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def next(phantom = false)
|
53
|
+
if tail?
|
54
|
+
if phantom
|
55
|
+
self
|
56
|
+
else
|
57
|
+
raise Errors::ZipperError,
|
58
|
+
"head.prev has no next"
|
59
|
+
end
|
60
|
+
else
|
61
|
+
HeadCursor.new(@node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def append(node)
|
66
|
+
HeadCursor.new(node.copy(next: @node))
|
67
|
+
end
|
68
|
+
|
69
|
+
def prepend(node)
|
70
|
+
HeadCursor.new(node.copy(next: @node))
|
71
|
+
end
|
72
|
+
|
73
|
+
def update(node = nil)
|
74
|
+
if node.nil?
|
75
|
+
HeadCursor.new(yield(nil).copy(next: @node))
|
76
|
+
else
|
77
|
+
HeadCursor.new(node.copy(next: @node))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def replace(node)
|
82
|
+
HeadCursor.new(node)
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete
|
86
|
+
if tail?
|
87
|
+
self
|
88
|
+
else
|
89
|
+
HeadCursor.new(@node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def truncate
|
94
|
+
HeadPrevCursor.new(nil)
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
if tail?
|
99
|
+
"Cursor([___])"
|
100
|
+
else
|
101
|
+
"Cursor([___, #{@node.inspect}, ...])"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def ==(other)
|
106
|
+
HeadPrevCursor === other and
|
107
|
+
not empty? ^ other.empty?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|