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
File without changes
File without changes
@@ -0,0 +1,90 @@
1
+ require "spec_helper"
2
+
3
+ describe Enumerator, "#unfold" do
4
+ it "halts on Fr.none" do
5
+ Enumerator.unfold(:seed){|n| Fr.none }.entries.should be_empty
6
+ end
7
+
8
+ it "generates collection lazily" do
9
+ # This wouldn't terminate otherwise
10
+ tens = Enumerator.unfold(10){|n| Fr.some([n, n]) }
11
+ tens.take(10).should == [10]*10
12
+ end
13
+
14
+ it "preserves order" do
15
+ xs = Enumerator.unfold(10){|n| (n > 0).maybe([n, n-1]) }
16
+ xs.entries.should == [10,9,8,7,6,5,4,3,2,1]
17
+ end
18
+
19
+ example "digits" do
20
+ radix = rand(64-1) + 2
21
+ number = rand(100000000)
22
+
23
+ digits = Enumerator.unfold(number){|n| (n > 0).maybe([n%radix, n/radix]) }
24
+ num, _ = digits.inject([0, 0]){|(n,k),d| [n+d*radix**k, k+1] }
25
+
26
+ num.should == number
27
+ end
28
+ end
29
+
30
+ describe Array, "#unfold" do
31
+ specify { Array.unfold(:seed){|n| Fr.none }.should be_empty }
32
+
33
+ it "preserves order" do
34
+ xs = Array.unfold(10){|n| (n > 0).maybe([n, n-1]) }
35
+ xs.entries.should == [10,9,8,7,6,5,4,3,2,1]
36
+ end
37
+
38
+ example "digits" do
39
+ radix = rand(64-1) + 2
40
+ number = rand(100000000)
41
+
42
+ digits = Array.unfold(number){|n| (n > 0).maybe([n%radix, n/radix]) }
43
+ num, _ = digits.inject([0, 0]){|(n,k),d| [n+d*radix**k, k+1] }
44
+
45
+ num.should == number
46
+ end
47
+
48
+ example "walk to total" do
49
+ # If I take steps of random sizes, between -10 and +10 units, how
50
+ # many steps do I need to take to accumulate +50 total units?
51
+ steps = Array.unfold(0) do |total|
52
+ step = rand(21)-10
53
+ (total < 50).maybe([step, total + step])
54
+ end
55
+
56
+ steps.min.should >= -10
57
+ steps.max.should <= +10
58
+
59
+ steps.reduce(:+).should >= 50
60
+ steps.reduce(:+).should <= 60
61
+ end
62
+ end
63
+
64
+ describe Array, "#unfoldR" do
65
+ specify { Array.unfoldR(:seed){|n| Fr.none }.should be_empty }
66
+
67
+ it "preserves order" do
68
+ xs = Array.unfoldR(10){|n| (n > 0).maybe([n, n-1]) }
69
+ xs.entries.should == [1,2,3,4,5,6,7,8,9,10]
70
+ end
71
+
72
+ example "digits" do
73
+ radix = rand(64-1) + 2
74
+ number = rand(100000000)
75
+ radlog = Integer(Math.log(number)/Math.log(radix))
76
+
77
+ digits = Array.unfoldR(number){|n| (n > 0).maybe([n%radix, n/radix]) }
78
+ num, n = digits.inject([0, radlog]){|(n,k),d| [n+d*radix**k, k-1] }
79
+
80
+ num.should == number
81
+ end
82
+ end
83
+
84
+ describe Hash, "#unfold" do
85
+ specify { Hash.unfold(:seed){|n| Fr.none }.should be_empty }
86
+ end
87
+
88
+ describe Set, "#unfold" do
89
+ specify { Set.unfold(:seed){|n| Fr.none }.should be_empty }
90
+ end
@@ -0,0 +1,23 @@
1
+ require "rspec"
2
+ require "fr"
3
+
4
+ require "simplecov"
5
+ SimpleCov.start
6
+
7
+ # Require supporting files with custom matchers and macros
8
+ Pathname.new(File.dirname(__FILE__)).tap do |specdir|
9
+ Dir["#{specdir}/support/**/*.rb"].each do |file|
10
+ require Pathname.new(file).relative_path_from(specdir)
11
+ end
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ # rspec -I lib -t random spec
16
+ # config.filter_run :random => true
17
+
18
+ # rspec -I lib -t ~random spec
19
+ # config.filter_run_excluding :random => true
20
+ # config.filter_run(:focus => true)
21
+
22
+ # srand 44182052595481443184625304627718313206
23
+ end
@@ -0,0 +1,225 @@
1
+ module Fr
2
+ module SZipper
3
+
4
+ class Model
5
+ attr_reader :position
6
+
7
+ def self.build(node = nil)
8
+ if node.nil?
9
+ Model.new(-1, [])
10
+ else
11
+ Model.new(0, Array.unfold(node){|seed| (!seed.nil?).maybe([seed, seed.next]) })
12
+ end
13
+ end
14
+
15
+ def initialize(position, elements)
16
+ @position, @elements =
17
+ position, elements.map.with_index{|node, k| node.copy(next: elements[k+1]) }
18
+ end
19
+
20
+ def copy(changes = {})
21
+ Model.new \
22
+ changes.fetch(:position, @position),
23
+ changes.fetch(:elements, @elements)
24
+ end
25
+
26
+ def node
27
+ if @position >= 0 and @position < @elements.length
28
+ @elements[@position]
29
+ else
30
+ raise Errors::ZipperError,
31
+ "no node at this position"
32
+ end
33
+ end
34
+
35
+ # . .
36
+ # a b c -> a b c
37
+ def prev(phantom = false)
38
+ if @position.zero?
39
+ if phantom
40
+ copy(position: -1)
41
+ else
42
+ raise Errors::ZipperError,
43
+ "no prev element"
44
+ end
45
+ else
46
+ copy(position: @position - 1)
47
+ end
48
+ end
49
+
50
+ # . .
51
+ # a b c -> a b c
52
+ def next(phantom = false)
53
+ if @position >= @elements.size - 1
54
+ if phantom
55
+ copy(position: @elements.size)
56
+ else
57
+ raise Errors::ZipperError,
58
+ "no next element"
59
+ end
60
+ else
61
+ copy(position: @position + 1)
62
+ end
63
+ end
64
+
65
+ def head?
66
+ @elements.empty? or @position.zero?
67
+ end
68
+
69
+ def tail?
70
+ @elements.empty? or @position == @elements.size - 1
71
+ end
72
+
73
+ def empty?
74
+ @elements.empty? or @position >= @elements.size
75
+ end
76
+
77
+ # .
78
+ # a b c
79
+ def head
80
+ if @elements.empty?
81
+ self
82
+ else
83
+ copy(position: 0)
84
+ end
85
+ end
86
+
87
+ # .
88
+ # a b c
89
+ def tail
90
+ if @elements.empty?
91
+ self
92
+ else
93
+ copy(position: @elements.size - 1)
94
+ end
95
+ end
96
+
97
+ # . .
98
+ # a b c -> a b X Y Z c
99
+ def append(node)
100
+ enum = unfold(node)
101
+
102
+ if @elements.empty?
103
+ copy(position: -1,
104
+ elements: enum)
105
+ elsif @position >= @elements.size - 1
106
+ # tail.next
107
+ copy(position: @elements.size,
108
+ elements: @elements.dup.concat(enum))
109
+ elsif @position < 0
110
+ copy(position: 0,
111
+ elements: @elements.dup.unshift(*enum))
112
+ else
113
+ copy(position: @position,
114
+ elements: @elements.dup.insert(@position + 1, *enum))
115
+ end
116
+ end
117
+
118
+ # . .
119
+ # a b c -> a X Y Z b c
120
+ def prepend(node)
121
+ enum = unfold(node)
122
+
123
+ if @elements.empty?
124
+ copy(position: enum.size - 1,
125
+ elements: enum)
126
+ elsif @position >= @elements.size
127
+ # tail.next
128
+ copy(position: @elements.size + enum.size,
129
+ elements: @elements.dup.insert(@position, *enum))
130
+ elsif @position < 0
131
+ # head.prev
132
+ copy(position: 0,
133
+ elements: @elements.dup.unshift(*enum))
134
+ else
135
+ copy(position: @position + enum.size,
136
+ elements: @elements.dup.insert(@position, *enum))
137
+ end
138
+ end
139
+
140
+ # . .
141
+ # a b c -> a X c
142
+ def update(node)
143
+ enum = unfold(node)
144
+
145
+ if @elements.empty?
146
+ copy(position: 0,
147
+ elements: enum)
148
+ elsif @position >= @elements.size
149
+ # tail.next
150
+ copy(position: @elements.size,
151
+ elements: @elements.dup.push(enum.first))
152
+ elsif @position < 0
153
+ # head.prev
154
+ copy(position: 0,
155
+ elements: @elements.dup.unshift(enum.first))
156
+ else
157
+ copy(position: @position,
158
+ elements: @elements.dup.tap{|e| e[@position] = enum.first })
159
+ end
160
+ end
161
+
162
+ # . .
163
+ # a b c -> a X Y Z
164
+ def replace(node = nil)
165
+ enum = node && unfold(node)
166
+
167
+ if @elements.empty?
168
+ copy(position: 0,
169
+ elements: enum || unfold(yield(nil)))
170
+ elsif @position < 0
171
+ # head.prev
172
+ copy(position: 0,
173
+ elements: enum || unfold(yield(@elements.slice(0, 1))))
174
+ elsif @position >= @elements.size - 1
175
+ # tail.next
176
+ copy(position: @elements.size,
177
+ elements: @elements + enum || unfold(yield(nil)))
178
+ else
179
+ copy(position: @position,
180
+ elements: @elements.slice(0, @position) + enum || unfold(yield(@elements.slice(@position, 1))))
181
+ end
182
+ end
183
+
184
+ # . .
185
+ # a b c -> a c
186
+ def delete
187
+ if @elements.empty?
188
+ self
189
+ elsif @position < 0
190
+ # head.prev
191
+ copy(position: 0,
192
+ elements: @elements)
193
+ elsif @position >= @elements.size - 1
194
+ # tail.next
195
+ copy(position: @elements.size - 1,
196
+ elements: @elements)
197
+ else
198
+ copy(position: @position,
199
+ elements: @elements.dup.tap{|e| e.delete_at(@position) })
200
+ end
201
+ end
202
+
203
+ # . .
204
+ # a b c -> a
205
+ def truncate
206
+ if @elements.empty?
207
+ self
208
+ elsif @position < 0
209
+ copy(position: -1,
210
+ elements: [])
211
+ else
212
+ copy(position: @position,
213
+ elements: @elements[0, @position + 1])
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ def unfold(node)
220
+ Array.unfold(node){|seed| (!seed.nil?).maybe([seed, seed.next]) }
221
+ end
222
+ end
223
+
224
+ end
225
+ end
@@ -0,0 +1,144 @@
1
+ module Fr
2
+ module SZipper
3
+
4
+ class Nodel
5
+
6
+ def self.build(node = nil)
7
+ if node.nil?
8
+ Model.new(nil, [])
9
+ else
10
+ Model.new(Array.unfold(node){|seed| (!seed.nil?).maybe([seed, seed.next]) }, [])
11
+ end
12
+ end
13
+
14
+ def initialize(node, init)
15
+ orderd = init.reverse
16
+ length = init.length
17
+
18
+ @node, @init =
19
+ node, init.map.with_index{|n, k| n.copy(next: orderd[length - k] || node) }
20
+ end
21
+
22
+ def copy(changes = {})
23
+ Model.new \
24
+ changes.fetch(:node, @node),
25
+ changes.fetch(:init, @init)
26
+ end
27
+
28
+ def node
29
+ if @node.nil?
30
+ raise Errors::ZipperError,
31
+ "no node at this position"
32
+ else
33
+ @node
34
+ end
35
+ end
36
+
37
+ # . .
38
+ # a b c -> a b c
39
+ def prev(phantom = false)
40
+ if @init.empty?
41
+ if phantom
42
+ # todo
43
+ else
44
+ raise Errors::ZipperError,
45
+ "no prev node"
46
+ end
47
+ else
48
+ copy(node: @init.head.copy(next: @node),
49
+ init: @init.tail)
50
+ end
51
+ end
52
+
53
+ # . .
54
+ # a b c -> a b c
55
+ def next(phantom = false)
56
+ if @node.nil?
57
+ # todo
58
+ elsif tail? and not phantom
59
+ raise Errors::ZipperError,
60
+ "no next node"
61
+ else
62
+ copy(node: @node.next,
63
+ init: @node.cons(@init))
64
+ end
65
+ end
66
+
67
+ def head?
68
+ @init.empty?
69
+ end
70
+
71
+ def tail?
72
+ if @node.nil?
73
+ @init.empty?
74
+ else
75
+ @node.next.nil?
76
+ end
77
+ end
78
+
79
+ def empty?
80
+ @node.nil?
81
+ end
82
+
83
+ # .
84
+ # a b c
85
+ def head
86
+ copy(init: [],
87
+ node: @init.last)
88
+ end
89
+
90
+ # .
91
+ # a b c
92
+ def tail
93
+ unless tail?
94
+ self.next.tail
95
+ else
96
+ self
97
+ end
98
+ end
99
+
100
+ # . .
101
+ # a b c -> a b X Y Z c
102
+ def append(node)
103
+ copy(node: @node.copy(next: node))
104
+ end
105
+
106
+ # . .
107
+ # a b c -> a X Y Z b c
108
+ def prepend(node)
109
+ copy(init: @init + unfold(node))
110
+ end
111
+
112
+ # . .
113
+ # a b c -> a X c
114
+ def update(node)
115
+ copy(node: node.copy(next: @node.next))
116
+ end
117
+
118
+ # . .
119
+ # a b c -> a X Y Z
120
+ def replace(node = nil)
121
+ copy(node: node || yield(@node))
122
+ end
123
+
124
+ # . .
125
+ # a b c -> a c
126
+ def delete
127
+ copy(node: @node.copy(next: @node.next))
128
+ end
129
+
130
+ # . .
131
+ # a b c -> a
132
+ def truncate
133
+ copy(node: @node.copy(next: nil))
134
+ end
135
+
136
+ private
137
+
138
+ def unfold(node)
139
+ Array.unfold(node){|seed| (!seed.nil?).maybe([seed, seed.next]) }
140
+ end
141
+ end
142
+
143
+ end
144
+ end