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.
Files changed (64) hide show
  1. data/README.md +0 -0
  2. data/Rakefile +81 -0
  3. data/lib/fr.rb +44 -0
  4. data/lib/fr/applicative.rb +0 -0
  5. data/lib/fr/array.rb +168 -0
  6. data/lib/fr/boolean.rb +15 -0
  7. data/lib/fr/either.rb +94 -0
  8. data/lib/fr/errors.rb +5 -0
  9. data/lib/fr/errors/zipper_error.rb +8 -0
  10. data/lib/fr/functor.rb +11 -0
  11. data/lib/fr/functor/array.rb +15 -0
  12. data/lib/fr/functor/either.rb +17 -0
  13. data/lib/fr/functor/maybe.rb +17 -0
  14. data/lib/fr/maybe.rb +76 -0
  15. data/lib/fr/monad.rb +236 -0
  16. data/lib/fr/monad/array.rb +21 -0
  17. data/lib/fr/monad/either.rb +18 -0
  18. data/lib/fr/monad/maybe.rb +24 -0
  19. data/lib/fr/monad/random.rb +50 -0
  20. data/lib/fr/monad/reader.rb +45 -0
  21. data/lib/fr/monad/state.rb +62 -0
  22. data/lib/fr/monad/writer.rb +49 -0
  23. data/lib/fr/monoid.rb +52 -0
  24. data/lib/fr/monoid/array.rb +13 -0
  25. data/lib/fr/monoid/boolean.rb +23 -0
  26. data/lib/fr/monoid/either.rb +13 -0
  27. data/lib/fr/monoid/maybe.rb +15 -0
  28. data/lib/fr/monoid/numeric.rb +31 -0
  29. data/lib/fr/monoid/string.rb +11 -0
  30. data/lib/fr/numeric.rb +1 -0
  31. data/lib/fr/object.rb +45 -0
  32. data/lib/fr/string.rb +1 -0
  33. data/lib/fr/szipper.rb +36 -0
  34. data/lib/fr/szipper/abstract_cursor.rb +63 -0
  35. data/lib/fr/szipper/edited_cursor.rb +62 -0
  36. data/lib/fr/szipper/enumerable.rb +266 -0
  37. data/lib/fr/szipper/head_cursor.rb +71 -0
  38. data/lib/fr/szipper/head_prev_cursor.rb +112 -0
  39. data/lib/fr/szipper/memoized_cursor.rb +59 -0
  40. data/lib/fr/szipper/node.rb +67 -0
  41. data/lib/fr/szipper/tail_next_cursor.rb +68 -0
  42. data/lib/fr/thunk.rb +73 -0
  43. data/lib/fr/tzipper.rb +38 -0
  44. data/lib/fr/tzipper/abstract_cursor.rb +351 -0
  45. data/lib/fr/tzipper/dangling_cursor.rb +107 -0
  46. data/lib/fr/tzipper/edited_cursor.rb +161 -0
  47. data/lib/fr/tzipper/memoized_cursor.rb +129 -0
  48. data/lib/fr/tzipper/node.rb +57 -0
  49. data/lib/fr/tzipper/path.rb +125 -0
  50. data/lib/fr/tzipper/root_cursor.rb +124 -0
  51. data/lib/fr/unfold.rb +93 -0
  52. data/spec/examples/array.example +0 -0
  53. data/spec/examples/monads/array.example +0 -0
  54. data/spec/examples/monads/maybe.example +0 -0
  55. data/spec/examples/monads/random.example +0 -0
  56. data/spec/examples/monads/state.example +0 -0
  57. data/spec/examples/szipper.example +652 -0
  58. data/spec/examples/thunk.example +0 -0
  59. data/spec/examples/tzipper.example +0 -0
  60. data/spec/examples/unfold.example +90 -0
  61. data/spec/spec_helper.rb +23 -0
  62. data/spec/support/szipper/model.rb +225 -0
  63. data/spec/support/szipper/nodel.rb +144 -0
  64. metadata +116 -0
@@ -0,0 +1,59 @@
1
+ module Fr
2
+ module SZipper
3
+
4
+ class MemoizedCursor < AbstractCursor
5
+ # @return [#next, #tail?]
6
+ attr_reader :node
7
+
8
+ def initialize(node, prev)
9
+ @node, @prev =
10
+ node, prev
11
+ end
12
+
13
+ def prev(phantom = false)
14
+ @prev
15
+ end
16
+
17
+ def head?
18
+ false
19
+ end
20
+
21
+ def tail?
22
+ @node.tail?
23
+ end
24
+
25
+ def append(node)
26
+ EditedCursor.new(node.copy(next: @node.next), self)
27
+ end
28
+
29
+ def prepend(node)
30
+ EditedCursor.new(node.copy(next: @node), @prev)
31
+ end
32
+
33
+ def update(node = nil)
34
+ if node.nil?
35
+ EditedCursor.new(yield(@node).copy(next: @node.next), @prev)
36
+ else
37
+ EditedCursor.new(node.copy(next: @node.next), @prev)
38
+ end
39
+ end
40
+
41
+ def replace(node)
42
+ EditedCursor.new(node, @prev)
43
+ end
44
+
45
+ def delete
46
+ if @prev.head?
47
+ HeadCursor.new(@prev.node.copy(next: @node.next))
48
+ else
49
+ EditedCursor.new(@prev.node.copy(next: @node.next), @prev.prev)
50
+ end
51
+ end
52
+
53
+ def inspect
54
+ "Cursor([..., #{@node.inspect}, ...])"
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ module Fr
2
+ module SZipper
3
+
4
+ # Example implementation for a stream node. For the
5
+ # purposes of traversal with a zipper, nodes can be
6
+ # anything that implements #copy, #next, and #tail?
7
+ #
8
+ # You'll probably want access to some datum too, like
9
+ # #value. Otherwise you'll be traversing a stream of
10
+ # otherwise useless cells.
11
+ #
12
+ class Node
13
+ attr_reader :value
14
+
15
+ attr_reader :next
16
+
17
+ def initialize(value, _next = nil)
18
+ @value, @next =
19
+ value, _next
20
+ end
21
+
22
+ def tail?
23
+ @next.nil?
24
+ end
25
+
26
+ def copy(options = {})
27
+ Node.new \
28
+ options.fetch(:value, @value),
29
+ options.fetch(:next, @next)
30
+ end
31
+
32
+ def inspect
33
+ "Node(#{@value})"
34
+ end
35
+ end
36
+
37
+ class << Node
38
+ def cons(value, _next)
39
+ Node.new(value, _next)
40
+ end
41
+
42
+ def tail(value)
43
+ Node.new(value)
44
+ end
45
+
46
+
47
+ # Convenient constructor to build a chain of Nodes
48
+ # from any "sequential" enum. That excludes things
49
+ # like Hash, Set, and Range because their traversal
50
+ # order isn't deterministic. Otherwise, anything that
51
+ # implements #reverse and #inject can be converted.
52
+ #
53
+ def build(enum)
54
+ if enum.empty?
55
+ raise Errors::ZipperError,
56
+ "enum is empty"
57
+ end
58
+
59
+ tail, *rest = enum.reverse
60
+ rest.inject(tail(tail)) do |_next, value|
61
+ cons(value, _next)
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,68 @@
1
+ module Fr
2
+ module SZipper
3
+
4
+ class TailNextCursor < AbstractCursor
5
+ def initialize(prev)
6
+ @prev = prev
7
+ end
8
+
9
+ def empty?
10
+ true
11
+ end
12
+
13
+ def tail?
14
+ false
15
+ end
16
+
17
+ def head?
18
+ false
19
+ end
20
+
21
+ def tail
22
+ @prev
23
+ end
24
+
25
+ def prev(phantom = false)
26
+ @prev
27
+ end
28
+
29
+ def next(phantom = false)
30
+ if phantom
31
+ self
32
+ else
33
+ raise Errors::ZipperError,
34
+ "tail node has no next"
35
+ end
36
+ end
37
+
38
+ def append(node)
39
+ @prev.append(node)
40
+ end
41
+
42
+ def prepend(node)
43
+ @prev.append(node)
44
+ end
45
+
46
+ def update(node = nil)
47
+ if node.nil?
48
+ @prev.append(yield(nil).copy(next: nil))
49
+ else
50
+ @prev.append(node.copy(next: nil))
51
+ end
52
+ end
53
+
54
+ def replace(node)
55
+ EditedCursor.new(node, @prev)
56
+ end
57
+
58
+ def delete
59
+ @prev
60
+ end
61
+
62
+ def inspect
63
+ "Cursor([..., #{@prev.node.inspect}, ___])"
64
+ end
65
+ end
66
+
67
+ end
68
+ end
data/lib/fr/thunk.rb ADDED
@@ -0,0 +1,73 @@
1
+ module Fr
2
+
3
+ # Beware, there's no way to make a thunk value 100%
4
+ # compatible with the value it computes. For instance,
5
+ #
6
+ # String === "a" #=> true
7
+ # String === thunk { "a" } #=> false
8
+ #
9
+ # "a" == "a" #=> true
10
+ # "a" == thunk { "a" } #=> false
11
+ #
12
+ # x = "abc"
13
+ # y = thunk { x }
14
+ #
15
+ # x.equal?(x) #=> true
16
+ # y.equal?(y) #=> true
17
+ # x.equal?(y) #=> false
18
+ # y.equal?(x) #=> false
19
+ #
20
+ class Thunk < BasicObject
21
+ def initialize(block)
22
+ unless block.arity.zero?
23
+ raise ArgumentError,
24
+ "block must have zero arity"
25
+ end
26
+
27
+ @block = block
28
+ end
29
+
30
+ def thunk?
31
+ true
32
+ end
33
+
34
+ # Silence warnings about redefining __send__
35
+ verbose = $VERBOSE
36
+ $VERBOSE = nil
37
+
38
+ undef_method :!
39
+ undef_method :==
40
+ undef_method :!=
41
+ undef_method :instance_eval
42
+ undef_method :instance_exec
43
+
44
+ private
45
+
46
+ def __send__(name, *args, &block)
47
+ unless defined? @value
48
+ @value = @block.call
49
+ @block = nil
50
+ end
51
+
52
+ @value.__send__(name, *args, &block)
53
+ end
54
+
55
+ $VERBOSE = verbose
56
+
57
+ def method_missing(name, *args, &block)
58
+ unless defined? @value
59
+ @value = @block.call
60
+ @block = nil
61
+ end
62
+
63
+ @value.__send__(name, *args, &block)
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ class Object
70
+ def thunk?
71
+ false
72
+ end
73
+ end
data/lib/fr/tzipper.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Fr
2
+ module TZipper
3
+ autoload :AbstractCursor, "fr/tzipper/abstract_cursor"
4
+ autoload :DanglingCursor, "fr/tzipper/dangling_cursor"
5
+ autoload :EditedCursor, "fr/tzipper/edited_cursor"
6
+ autoload :MemoizedCursor, "fr/tzipper/memoized_cursor"
7
+ autoload :RootCursor, "fr/tzipper/root_cursor"
8
+
9
+ autoload :AbstractPath, "fr/tzipper/path"
10
+ autoload :Hole, "fr/tzipper/path"
11
+ autoload :Root, "fr/tzipper/path"
12
+ autoload :Node, "fr/tzipper/node"
13
+ end
14
+
15
+ class << TZipper
16
+
17
+ # Constructors a zipper for traversing the given tree or
18
+ # subtree. The `node` value can be any structure where
19
+ #
20
+ # node#children
21
+ # an array of child nodes
22
+ #
23
+ # node#leaf?
24
+ # true if the node is not allowed have children
25
+ #
26
+ # node#copy(children: [...])
27
+ # creates a new node with the given children
28
+ #
29
+ # node#cons(xs)
30
+ # returns a new list [node, *xs]
31
+ #
32
+ # @return [AbstractCursor]
33
+ def build(node)
34
+ Fr::TZipper::RootCursor.new(node)
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,351 @@
1
+ module Fr
2
+ module TZipper
3
+
4
+ class AbstractCursor
5
+
6
+ # @return [#leaf?, #children, #copy, #cons]
7
+ # abstract :node
8
+
9
+ # @return [AbstractPath]
10
+ # abstract :path
11
+
12
+ # @group Querying the Tree Location
13
+ #########################################################################
14
+
15
+ # (see AbstractPath#depth)
16
+ def depth
17
+ path.depth
18
+ end
19
+
20
+ # (see AbstractPath#first?)
21
+ def first?
22
+ path.first?
23
+ end
24
+
25
+ # (see AbstractPath#last?)
26
+ def last?
27
+ path.last?
28
+ end
29
+
30
+ # True if the node has no children
31
+ # abstract :leaf?
32
+
33
+ # True if the node has no parent
34
+ # abstract :root?
35
+
36
+ # Returns nodes between this zipper and the other, including `self.node`
37
+ # and `other.node` as end points. When this and the other zipper point to
38
+ # the same node within the tree, a single node is returned. Otherwise, the
39
+ # nodes are returned in order according to a left-to-right depth-first
40
+ # pre-order traversal.
41
+ #
42
+ # @note This method assumes `other` is a zipper for the same tree as the
43
+ # tree wrapped by `this`. In general, there is no way to know if that is
44
+ # or isn't the case, without comparing the entire tree. If this method
45
+ # is called on two different trees, the results are undefined.
46
+ #
47
+ # @return [Array]
48
+ def between(other)
49
+ # Collect ancestors of self, sorted oldest first (deepest last). This
50
+ # forms a boundary of nodes, which is called a "spine" below
51
+ zipper = self
52
+ lspine = [self]
53
+
54
+ until zipper.root?
55
+ zipper = zipper.up
56
+ lspine.unshift(zipper)
57
+ end
58
+
59
+ # Collect ancestors of self, sorted oldest first (deepest last). This
60
+ # forms a list of boundary nodes, which is called a "spine" below
61
+ zipper = other
62
+ rspine = [other]
63
+
64
+ until zipper.root?
65
+ zipper = zipper.up
66
+ rspine.unshift(zipper)
67
+ end
68
+
69
+ # Now we have two spines, both beginning with the root node. We remove
70
+ # the prefix common to both spines.
71
+ while a = lspine.first and b = rspine.first
72
+ if a.path == b.path
73
+ lspine.shift
74
+ rspine.shift
75
+ else
76
+ break
77
+ end
78
+ end
79
+
80
+ if lspine.empty?
81
+ # The other node is a child of self's node, and rspine contains all
82
+ # the nodes along the path between the two nodes, not including the
83
+ # self node.
84
+ return node.cons(rspine.map(&:node))
85
+
86
+ elsif rspine.empty?
87
+ # Self's node is a child of other's node, and lspine contains all
88
+ # the nodes along the path between the two nodes, not including the
89
+ # other node
90
+ return other.node.cons(lspine.map(&:node))
91
+
92
+ elsif lspine.head.path.position > rspine.head.path.position
93
+ # The first elements of lspine and rspine are siblings that share a
94
+ # common parent. Arrange them such that lspine is on the left, and
95
+ # so rspine is on the right
96
+ lspine, rspine = rspine, lspine
97
+ end
98
+
99
+ between = []
100
+
101
+ # Starting at the bottom of the left spine working upward, accumulate
102
+ # all the nodes to the right of the spine. Remember this is contained
103
+ # within the subtree under lspine.head
104
+ lspine.each{|z| between << z.node }
105
+ lspine.tail.reverse.each do |zipper|
106
+ until zipper.last?
107
+ zipper = zipper.next
108
+ between.concat(zipper.flatten)
109
+ end
110
+ end
111
+
112
+ # For the sibling nodes directly between (not including) lspine.head
113
+ # and rspine.head, we can accumulate the entire subtrees.
114
+ count = rspine.head.path.position - lspine.head.path.position - 1
115
+ zipper = lspine.head
116
+
117
+ count.times do
118
+ zipper = zipper.next
119
+ between.concat(zipper.flatten)
120
+ end
121
+
122
+ between << rspine.head.node
123
+
124
+ rspine.tail.each do |zipper|
125
+ count = zipper.path.position
126
+ zipper = zipper.first
127
+
128
+ # We have to do a bit more work to traverse the siblings in left-to-
129
+ # right order, because `zipper` is now the left spine. We start on
130
+ # the first sibling and move left a fixed number of times
131
+ count.times do
132
+ between.concat(zipper.flatten)
133
+ zipper = zipper.next
134
+ end
135
+
136
+ # Now zipper is along the left spine. We don't expand it here, but the
137
+ # next item in rspine is the next child along the left spine
138
+ between << zipper.node
139
+ end
140
+
141
+ between
142
+ end
143
+
144
+ # Flattens the subtree into an Array of nodes. The nodes are arranged
145
+ # according to a depth-first left-to-right preorder traversal.
146
+ #
147
+ # @return [Array]
148
+ def flatten
149
+ nodes = []
150
+ queue = [node]
151
+
152
+ while node = queue.shift
153
+ nodes << node
154
+ queue.unshift(*node.children) unless node.leaf?
155
+ end
156
+
157
+ nodes
158
+ end
159
+
160
+ # @group Traversing the Tree
161
+ #########################################################################
162
+
163
+ # Navigate to the first child node
164
+ #
165
+ # @return [MemoizedCursor]
166
+ def down
167
+ @__down ||= begin
168
+ if leaf?
169
+ raise Errors::ZipperError,
170
+ "cannot descend into leaf node"
171
+ end
172
+
173
+ head, *tail = @node.children
174
+
175
+ MemoizedCursor.new(head,
176
+ Hole.new([], @path, tail), self)
177
+ end
178
+ end
179
+
180
+ # Navigate to the first child node, or if there are no children,
181
+ # create a placeholder where the first child node will be placed
182
+ #
183
+ # @return [AbstractCursor]
184
+ def dangle
185
+ if node.leaf?
186
+ raise Errors::ZipperError,
187
+ "cannot descend into leaf node"
188
+ end
189
+
190
+ if leaf?
191
+ DanglingCursor.new(self)
192
+ else
193
+ down
194
+ end
195
+ end
196
+
197
+ # Navigate to the `nth` child node
198
+ #
199
+ # @return [AbstractCursor]
200
+ def child(n)
201
+ if n < 0
202
+ raise Errors::ZipperError,
203
+ "child index cannot be negative"
204
+ end
205
+
206
+ cursor = down
207
+ until n.zero?
208
+ cursor = cursor.next
209
+ n -= 1
210
+ end
211
+ cursor
212
+ end
213
+
214
+ # Returns a list of cursors, one pointing to each child node.
215
+ #
216
+ # @return [Array<MemoizedCursor>]
217
+ def children
218
+ children = []
219
+
220
+ unless leaf?
221
+ zipper = down
222
+ children << zipper
223
+
224
+ until zipper.last?
225
+ zipper = zipper.next
226
+ children << zipper
227
+ end
228
+ end
229
+
230
+ children
231
+ end
232
+
233
+ # Recursively descend to each node's `nth` child
234
+ #
235
+ # @return [AbstractCursor]
236
+ def descendant(n, *ns)
237
+ cursor = self
238
+
239
+ n.cons(ns).each do |n|
240
+ cursor = cursor.child(n)
241
+ end
242
+
243
+ cursor
244
+ end
245
+
246
+ # Navigate to the parent node
247
+ #
248
+ # @return [AbstractCursor]
249
+ # abstract :up
250
+
251
+ # Navigate to the next (rightward) sibling node
252
+ #
253
+ # @return [AbstractCursor]
254
+ # abstract :next
255
+
256
+ # Navigate to the previous (leftward) sibling node
257
+ #
258
+ # @return [AbstractCursor]
259
+ # abstract :prev
260
+
261
+ # Navigate to the first (leftmost) sibling node
262
+ #
263
+ # @return [AbstractCursor]
264
+ # abstract :first
265
+
266
+ # Navigate to the last (rightmost) sibling node
267
+ #
268
+ # @return [AbstractCursor]
269
+ # abstract :last
270
+
271
+ # Navigate to the root node
272
+ #
273
+ # @return [RootCursor]
274
+ def root
275
+ cursor = self
276
+ cursor = cursor.up until cursor.root?
277
+ cursor
278
+ end
279
+
280
+ # @group Editing the Tree
281
+ #########################################################################
282
+
283
+ # Insert a new sibling node after (to the right of) the current node,
284
+ # and navigate to the new sibling node
285
+ #
286
+ # @return [EditedCursor]
287
+ # abstract :append, :args => %w(sibling)
288
+
289
+ # Insert a new sibling node before (to the left of) the current node,
290
+ # and navigate to the new sibling node
291
+ #
292
+ # @return [EditedCursor]
293
+ # abstract :prepend, :args => %w(sibling)
294
+
295
+ # (see #append)
296
+ def insert_right(sibling)
297
+ append(sibling)
298
+ end
299
+
300
+ # (see #prepend)
301
+ def insert_left(sibling)
302
+ prepend(sibling)
303
+ end
304
+
305
+ # Insert a new child node before (to the left of) any existing children
306
+ # nodes and navigate to the new child node
307
+ #
308
+ # @return [EditedCursor]
309
+ def prepend_child(child)
310
+ if node.leaf?
311
+ raise Errors::ZipperError,
312
+ "cannot add child to leaf node"
313
+ end
314
+
315
+ EditedCursor.new(child,
316
+ Hole.new([], path, node.children), self)
317
+ end
318
+
319
+ # Insert a new child node after (to the right of) any existing children
320
+ # nodes and navigate to the new child node
321
+ #
322
+ # @return [EditedCursor]
323
+ def append_child(child)
324
+ if node.leaf?
325
+ raise Errors::ZipperError,
326
+ "cannot add child to leaf node"
327
+ end
328
+
329
+ EditedCursor.new(child,
330
+ Hole.new(node.children.reverse, path, []), self)
331
+ end
332
+
333
+ # Replace the current node with the given node
334
+ #
335
+ # @return [AbstractCursor]
336
+ # abstract :replace, :args => %w(node)
337
+
338
+ # Remove the current node, and navigate to the next (rightward) node if
339
+ # one exists. Otherwise, navigate to the previous (leftward) node if one
340
+ # exists. Otherwise, create a placeholder where the next sibling node will
341
+ # be created.
342
+ #
343
+ # @return [EditedCursor]
344
+ # abstract :delete
345
+
346
+ # @endgroup
347
+ #########################################################################
348
+ end
349
+
350
+ end
351
+ end