fr 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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