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,17 @@
|
|
1
|
+
#
|
2
|
+
# Because Either is a monad, we automatically derive
|
3
|
+
# the implementation for `map` from `liftM`.
|
4
|
+
#
|
5
|
+
# module Fr
|
6
|
+
# class << Either
|
7
|
+
# include Functor
|
8
|
+
#
|
9
|
+
# def map(f = nil, x = nil, &block)
|
10
|
+
# (x.nil?) ?
|
11
|
+
# (block_given?) ?
|
12
|
+
# f.map(&block) : # f.map(fa){ .. }
|
13
|
+
# lambda{|x| x.map(&f) } :# f.map(lambda{ .. }).call(fa)
|
14
|
+
# x.map(&f) # f.map(lambda{ .. }, fa)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Because Either is a monad, we automatically derive
|
3
|
+
# the implementation for `map` from `liftM`.
|
4
|
+
#
|
5
|
+
# module Fr
|
6
|
+
# class << Maybe
|
7
|
+
# include Functor
|
8
|
+
#
|
9
|
+
# def map(f = nil, x = nil, &block)
|
10
|
+
# (x.nil?) ?
|
11
|
+
# (block_given?) ?
|
12
|
+
# f.map(&block) : # f.map(fa){ .. }
|
13
|
+
# lambda{|x| x.map(&f) } :# f.map(lambda{ .. }).call(fa)
|
14
|
+
# x.map(&f) # f.map(lambda{ .. }, fa)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
data/lib/fr/maybe.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
class Maybe
|
4
|
+
class Some < Maybe
|
5
|
+
def initialize(value)
|
6
|
+
@value = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def map
|
10
|
+
Some.new(yield @value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fold(default)
|
14
|
+
yield @value
|
15
|
+
end
|
16
|
+
|
17
|
+
def some?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def none?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
Maybe === other and other.fold(false){|x| x == @value }
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :eql?, :==
|
30
|
+
|
31
|
+
def hash
|
32
|
+
@value.hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"Fr.some(#{@value.inspect})"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class None_ < Maybe
|
41
|
+
def map
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def fold(default)
|
46
|
+
default
|
47
|
+
end
|
48
|
+
|
49
|
+
def some?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def none?
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
None_ === other
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :eql?, :==
|
62
|
+
|
63
|
+
def inspect
|
64
|
+
"Fr.none"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Singleton instance
|
69
|
+
None = None_.new
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
require "fr/functor/maybe"
|
75
|
+
require "fr/monoid/maybe"
|
76
|
+
require "fr/monad/maybe"
|
data/lib/fr/monad.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
module Fr
|
2
|
+
module Monad
|
3
|
+
# include Applicative
|
4
|
+
include Functor
|
5
|
+
|
6
|
+
# Perform each action, from left to right
|
7
|
+
# :: Monad m => [m a] -> m [a]
|
8
|
+
#
|
9
|
+
# @todo: Need trampoline for 2500+ ms
|
10
|
+
def sequence(ms)
|
11
|
+
ms.inject(unit []) do |prev, curr|
|
12
|
+
bind(prev) do |xs|
|
13
|
+
bind(curr) do |x|
|
14
|
+
unit(xs + [x])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Remove one level of monadic structure
|
21
|
+
# :: Monad m => m (m a) -> m a
|
22
|
+
#
|
23
|
+
# >> m.join(m.unit(m.unit(100)))
|
24
|
+
# => m.unit(100)
|
25
|
+
#
|
26
|
+
# >> Array.join([[1],[2],[3]])
|
27
|
+
# => [1,2,3]
|
28
|
+
#
|
29
|
+
def join(mm)
|
30
|
+
bind(mm){|x| x }
|
31
|
+
end
|
32
|
+
|
33
|
+
# :: Monad m => (a -> m b) -> [a] -> m [b]
|
34
|
+
#
|
35
|
+
# >> m.filter(lambda{|a| m.unit(a.even?) }).call([0,1,2,3])
|
36
|
+
# => m.unit([true,false,true,false])
|
37
|
+
#
|
38
|
+
def mapM(f, as = nil)
|
39
|
+
if as.nil?
|
40
|
+
lambda do |as|
|
41
|
+
sequence(as.map(&f))
|
42
|
+
end
|
43
|
+
else
|
44
|
+
sequence(as.map(&f))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# :: Monad m => [a] -> (a -> m b) -> m [b]
|
49
|
+
#
|
50
|
+
# >> m.forM([0,1,2,3]){|a| m.unit(a.even?) }
|
51
|
+
# => m.unit([true,false,true,false])
|
52
|
+
#
|
53
|
+
# >> m.forM([0,1,2,3], lambda{|a| m.unit(a.even?) })
|
54
|
+
# => m.unit([true,false,true,false])
|
55
|
+
#
|
56
|
+
# >> m.forM([0,1,2,3]).call(lambda{|a| m.unit(a.even?) })
|
57
|
+
# => m.unit([true,false,true,false])
|
58
|
+
#
|
59
|
+
def forM(as, f = nil, &block)
|
60
|
+
rest =
|
61
|
+
lambda do |f|
|
62
|
+
sequence(as.map(&block))
|
63
|
+
end
|
64
|
+
|
65
|
+
(f.nil?) ?
|
66
|
+
(block_given?) ?
|
67
|
+
rest.call(block) : rest : rest.call(f)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Left-to-right Kleisli composition of monads
|
71
|
+
# :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
|
72
|
+
#
|
73
|
+
# >> m.composeM(lambda{|a| m.unit(a.to_s) },
|
74
|
+
# lambda{|b| m.unit(b.length) }).call(450)
|
75
|
+
# => m.unit(3)
|
76
|
+
#
|
77
|
+
def composeM(f, g = nil, &block)
|
78
|
+
rest =
|
79
|
+
lambda do |g|
|
80
|
+
lambda do |a|
|
81
|
+
bind(f.call(a)) do |b|
|
82
|
+
g.call(b)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
(g.nil?) ?
|
88
|
+
(block_given?) ?
|
89
|
+
rest.call(block) : rest : rest.call(g)
|
90
|
+
end
|
91
|
+
|
92
|
+
# :: Monad m => (a -> m Boolean) -> [a] -> m [a]
|
93
|
+
#
|
94
|
+
# >> m.filterM(lambda{|a| m.unit(a.even?) }).call([0,1,2,3,4])
|
95
|
+
# => m.unit([0,2,4])
|
96
|
+
#
|
97
|
+
# Not implemented:
|
98
|
+
# >> [0,1,2,3,4].filterM{|a| m.unit(a.even?) }
|
99
|
+
# => m.unit([0,2,4])
|
100
|
+
#
|
101
|
+
def filterM(f, as = nil)
|
102
|
+
rec =
|
103
|
+
lambda do |as|
|
104
|
+
if as.empty?
|
105
|
+
unit([])
|
106
|
+
else
|
107
|
+
a, *as = as
|
108
|
+
bind(f.call(a)) do |bool|
|
109
|
+
bind(rec.call(as)) do |rest|
|
110
|
+
unit(bool ?
|
111
|
+
a.cons(rest) : rest)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
(as.nil?) ?
|
118
|
+
rec : rec.call(as)
|
119
|
+
end
|
120
|
+
|
121
|
+
# :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c]
|
122
|
+
#
|
123
|
+
# >> m.zipM(lambda{|a,b| m.unit(a + b) }).call([1,2,3]).call([3,2,1])
|
124
|
+
# => m.unit([6,6,6])
|
125
|
+
#
|
126
|
+
# Not implemented:
|
127
|
+
# >> [1,2,3].zipM([3,2,1]){|a,b| m.unit(a + b) }
|
128
|
+
# => m.unit([6,6,6])
|
129
|
+
#
|
130
|
+
def zipM(f, xs = nil, ys = nil)
|
131
|
+
rest =
|
132
|
+
lambda do |xs|
|
133
|
+
lambda do |ys|
|
134
|
+
sequence(xs.zip(ys).map{|(x,y)| f.call(x, y) })
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
(ys.nil?) ?
|
139
|
+
(xs.nil?) ?
|
140
|
+
rest : rest.call(xs) : rest.call(xs).call(ys)
|
141
|
+
end
|
142
|
+
|
143
|
+
# :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
|
144
|
+
#
|
145
|
+
# >> m.foldM(lambda{|a,b| m.unit(a | b) }).call(0).call([1,2,4])
|
146
|
+
# => m.unit(7)
|
147
|
+
#
|
148
|
+
# Not implemented:
|
149
|
+
# >> [1,2,4].foldM(0){|a,b| m.unit(a | b) }
|
150
|
+
# => m.unit(7)
|
151
|
+
#
|
152
|
+
def foldM(f, seed = nil, bs = nil)
|
153
|
+
rest =
|
154
|
+
lambda do |seed|
|
155
|
+
lambda do |bs|
|
156
|
+
if bs.empty?
|
157
|
+
unit(seed)
|
158
|
+
else
|
159
|
+
b, *bs = bs
|
160
|
+
bind(f.call(seed, b)) do |seed_|
|
161
|
+
rec.call(seed_).call(bs)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
(bs.nil?) ?
|
168
|
+
(seed.nil?) ?
|
169
|
+
rest : rest.call(seed) : rest.call(seed).call(bs)
|
170
|
+
end
|
171
|
+
|
172
|
+
# :: Monad m => Bool -> m () -> m ()
|
173
|
+
def whenM(condition)
|
174
|
+
# todo
|
175
|
+
end
|
176
|
+
|
177
|
+
# :: Monad m => Bool -> m () -> m ()
|
178
|
+
def unlessM(condition)
|
179
|
+
# todo
|
180
|
+
end
|
181
|
+
|
182
|
+
# :: Monad m => (a -> b) -> m a -> m b
|
183
|
+
#
|
184
|
+
# >> m.liftM(lambda{|x| x.even? }).call(m.unit(30))
|
185
|
+
# => m.unit(true)
|
186
|
+
#
|
187
|
+
def liftM(f = nil, ma = nil, &block)
|
188
|
+
rest =
|
189
|
+
lambda do |f|
|
190
|
+
lambda do |ma|
|
191
|
+
bind(ma) do |a|
|
192
|
+
unit(f.call(a))
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
(ma.nil?) ?
|
198
|
+
(block_given?) ?
|
199
|
+
rest.call(block).call(f) :# m.liftM(ma){ .. }
|
200
|
+
rest.call(f) :# m.liftM(lambda{ .. }).call(ma)
|
201
|
+
rest.call(f).call(ma) # m.liftM(lambda{ .. }, ma)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Automatically derive an implementation of `Functor.map` from `liftM`.
|
205
|
+
#
|
206
|
+
# Notice
|
207
|
+
# liftM :: Monad m => (a -> b) -> m a -> m b
|
208
|
+
# map :: Functor f => (a -> b) -> f a -> f b
|
209
|
+
alias_method :map, :liftM
|
210
|
+
|
211
|
+
# :: Monad m => m (a -> b) -> m a -> m b
|
212
|
+
#
|
213
|
+
# >> mf = m.unit(lambda{|x| lambda{|y| x + y }})
|
214
|
+
#
|
215
|
+
# >> ab = m.applyM(mf)
|
216
|
+
# >> ma = m.applyM(ab.call(m.unit 3))
|
217
|
+
# >> mb = ma.call(m.unit 4)
|
218
|
+
#
|
219
|
+
# => m.unit(7)
|
220
|
+
#
|
221
|
+
def applyM(mf, ma = nil)
|
222
|
+
rest =
|
223
|
+
lambda do |ma|
|
224
|
+
bind(mf) do |f|
|
225
|
+
bind(ma) do |a|
|
226
|
+
unit(f.call(a))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
(ma.nil?) ?
|
232
|
+
rest : rest.call(ma)
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class << Array
|
2
|
+
include Fr::Monad
|
3
|
+
|
4
|
+
# Evaluators
|
5
|
+
#############################################
|
6
|
+
|
7
|
+
def run(computation)
|
8
|
+
computation
|
9
|
+
end
|
10
|
+
|
11
|
+
# Combinators
|
12
|
+
#############################################
|
13
|
+
|
14
|
+
def unit(value)
|
15
|
+
[value]
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind(x, &f)
|
19
|
+
x.map(&f).inject([], &:concat)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
class << Either
|
4
|
+
include Monad
|
5
|
+
|
6
|
+
# Combinators
|
7
|
+
#############################################
|
8
|
+
|
9
|
+
def unit(value)
|
10
|
+
Either::Right.new(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def bind(x, &f)
|
14
|
+
x.fold(lambda{|l| x }, lambda{|r| f.call(r) })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
class << Maybe
|
4
|
+
include Monad
|
5
|
+
|
6
|
+
# Evaluators
|
7
|
+
#############################################
|
8
|
+
def run(computation)
|
9
|
+
computation
|
10
|
+
end
|
11
|
+
|
12
|
+
# Combinators
|
13
|
+
#############################################
|
14
|
+
|
15
|
+
def unit(value)
|
16
|
+
Maybe::Some.new(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def bind(x, &f)
|
20
|
+
x.fold(x, &f)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
class << Random
|
4
|
+
include Monad
|
5
|
+
|
6
|
+
# Evaluators
|
7
|
+
#############################################
|
8
|
+
|
9
|
+
def run(computation, scale = 1.0)
|
10
|
+
computation.call(scale)
|
11
|
+
end
|
12
|
+
|
13
|
+
def eval(computation, scale = 1.0)
|
14
|
+
computation.call(scale)[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Combinators
|
18
|
+
#############################################
|
19
|
+
|
20
|
+
def unit(value)
|
21
|
+
lambda do |scale|
|
22
|
+
[value, scale]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def bind(f, &g)
|
27
|
+
lambda do |scale|
|
28
|
+
value, scale = f.call(scale)
|
29
|
+
g.call(value).call(scale)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Actions
|
34
|
+
#############################################
|
35
|
+
|
36
|
+
def rand(limit = nil)
|
37
|
+
lambda do |scale|
|
38
|
+
[Kernel.rand(limit), scale]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def scale(number, zero)
|
43
|
+
# Linearly scale given number towards zero
|
44
|
+
lambda do |scale|
|
45
|
+
[zero + scale * (number - zero), scale]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|