mon 0.0.2
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.
- checksums.yaml +7 -0
- data/lib/contracts/contract_helpers.rb +12 -0
- data/lib/contracts/future.rb +28 -0
- data/lib/contracts/lazy.rb +27 -0
- data/lib/contracts/list.rb +22 -0
- data/lib/contracts/maybe.rb +15 -0
- data/lib/contracts/monad_contract.rb +57 -0
- data/lib/contracts/reactron.rb +23 -0
- data/lib/contracts/try.rb +23 -0
- data/lib/mon.rb +21 -0
- data/lib/monads/chainable_monad.rb +23 -0
- data/lib/monads/future.rb +154 -0
- data/lib/monads/lazy.rb +200 -0
- data/lib/monads/list.rb +89 -0
- data/lib/monads/maybe.rb +211 -0
- data/lib/monads/monad.rb +11 -0
- data/lib/monads/reactron.rb +175 -0
- data/lib/monads/try.rb +190 -0
- data/spec/contract_spec.rb +310 -0
- data/spec/monad_spec.rb +757 -0
- metadata +77 -0
data/lib/monads/try.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
|
2
|
+
module Mon
|
3
|
+
|
4
|
+
module Monad
|
5
|
+
|
6
|
+
require_relative 'chainable_monad'
|
7
|
+
require_relative 'monad'
|
8
|
+
|
9
|
+
class Try < Monad
|
10
|
+
include ChainableMonad
|
11
|
+
|
12
|
+
def self.[](obj)
|
13
|
+
if obj.is_a? Failure
|
14
|
+
Failure[obj.get]
|
15
|
+
elsif obj
|
16
|
+
Success[obj]
|
17
|
+
else
|
18
|
+
Failure[false]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self::to *obj, &f
|
23
|
+
begin
|
24
|
+
if obj.length == 1 and obj[0].is_a? Proc
|
25
|
+
raise RuntimeError("Provided too many procs!") if f
|
26
|
+
Success[obj.call]
|
27
|
+
elsif f and obj.length > 0
|
28
|
+
Success[f.call(*obj)]
|
29
|
+
else
|
30
|
+
raise RuntimeError("Failed to provide block") if not f
|
31
|
+
Success[f.call]
|
32
|
+
end
|
33
|
+
rescue StandardError => e
|
34
|
+
Failure[e]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self::valid?(o)
|
39
|
+
o.is_a? self.class
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
protected :new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Success < Try
|
48
|
+
|
49
|
+
def initialize(obj)
|
50
|
+
if obj.is_a? Success
|
51
|
+
@obj = obj.get
|
52
|
+
else
|
53
|
+
@obj = obj
|
54
|
+
end
|
55
|
+
super()
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.[](obj)
|
59
|
+
if (obj.is_a? Failure) or (obj.is_a? Success)
|
60
|
+
obj
|
61
|
+
else
|
62
|
+
Success.new(obj)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def bind &fun
|
67
|
+
begin
|
68
|
+
Success[fun.call(@obj)]
|
69
|
+
rescue StandardError => e
|
70
|
+
Failure[e]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def _canBind?(name)
|
75
|
+
@obj.respond_to? name
|
76
|
+
end
|
77
|
+
|
78
|
+
def or obj, &f
|
79
|
+
@obj
|
80
|
+
end
|
81
|
+
|
82
|
+
def orFail
|
83
|
+
@obj
|
84
|
+
end
|
85
|
+
|
86
|
+
def unwrap
|
87
|
+
@obj
|
88
|
+
end
|
89
|
+
|
90
|
+
def eql?(other)
|
91
|
+
if other.is_a? Success
|
92
|
+
@obj == other.unwrap
|
93
|
+
else
|
94
|
+
@obj == other
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def ==(o)
|
99
|
+
eql?(o)
|
100
|
+
end
|
101
|
+
|
102
|
+
def equal?(o)
|
103
|
+
eql?(o)
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_s
|
107
|
+
"Success[#{@obj}]"
|
108
|
+
end
|
109
|
+
|
110
|
+
def self::valid?(o)
|
111
|
+
o.is_a? self.class
|
112
|
+
end
|
113
|
+
|
114
|
+
class << self
|
115
|
+
protected :new
|
116
|
+
end
|
117
|
+
|
118
|
+
protected :unwrap, :_canBind?
|
119
|
+
end
|
120
|
+
|
121
|
+
class Failure < Try
|
122
|
+
|
123
|
+
def initialize(err)
|
124
|
+
@err = err
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.[](err)
|
128
|
+
self.new(err)
|
129
|
+
end
|
130
|
+
|
131
|
+
def bind &fun
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
def or obj, &f
|
136
|
+
if f and obj
|
137
|
+
f.call(obj)
|
138
|
+
elsif f
|
139
|
+
f.call
|
140
|
+
else
|
141
|
+
obj
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def orFail &f
|
146
|
+
if f
|
147
|
+
f.call(@err)
|
148
|
+
elsif @err.is_a? Exception
|
149
|
+
raise @err
|
150
|
+
else
|
151
|
+
@err
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def unwrap
|
156
|
+
@err
|
157
|
+
end
|
158
|
+
|
159
|
+
def _canBind? name
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
"Failure[#{@err}]"
|
165
|
+
end
|
166
|
+
|
167
|
+
def eql?(other)
|
168
|
+
(other.is_a? Failure) ? (@err == other.unwrap) : false
|
169
|
+
end
|
170
|
+
|
171
|
+
def ==(o)
|
172
|
+
eql?(o)
|
173
|
+
end
|
174
|
+
|
175
|
+
def equal?(o)
|
176
|
+
eql?(o)
|
177
|
+
end
|
178
|
+
|
179
|
+
def self::valid?(o)
|
180
|
+
o.is_a? self.class
|
181
|
+
end
|
182
|
+
|
183
|
+
class << self
|
184
|
+
protected :new
|
185
|
+
end
|
186
|
+
|
187
|
+
protected :unwrap, :_canBind?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
|
2
|
+
require 'contracts'
|
3
|
+
require_relative '../lib/mon'
|
4
|
+
|
5
|
+
include Contracts
|
6
|
+
|
7
|
+
Contract Mon::C::List[Num] => Mon::C::List[Num]
|
8
|
+
def listSquare(i)
|
9
|
+
i.bind { |i| i * i }
|
10
|
+
end
|
11
|
+
|
12
|
+
Contract Mon::C::Maybe[Num] => Mon::C::Maybe[Num]
|
13
|
+
def maybeSquare(i)
|
14
|
+
i.bind { |i| i * i }
|
15
|
+
end
|
16
|
+
|
17
|
+
Contract Mon::C::Try[Num] => Mon::C::Try[Num]
|
18
|
+
def trySquare(i)
|
19
|
+
i.bind { |i| i * i }
|
20
|
+
end
|
21
|
+
|
22
|
+
Contract Mon::C::Lazy[Num] => Mon::C::Lazy[Num]
|
23
|
+
def lazySquare(i)
|
24
|
+
i.bind { |i| i * i }
|
25
|
+
end
|
26
|
+
|
27
|
+
Contract Mon::C::Future[Num] => Mon::C::Future[Num]
|
28
|
+
def futureSquare(i)
|
29
|
+
i.bind { |i| i * i }
|
30
|
+
end
|
31
|
+
|
32
|
+
Contract Mon::C::React[Num] => Mon::C::React[Num]
|
33
|
+
def reactSquare(i)
|
34
|
+
i.bind { |i| i * i }
|
35
|
+
end
|
36
|
+
|
37
|
+
Contract Mon::C::Monad[Num] => Mon::C::Monad[Num]
|
38
|
+
def monadSquare(i)
|
39
|
+
i.bind { |i| i * i }
|
40
|
+
end
|
41
|
+
|
42
|
+
Contract Mon::C::Try[Mon::C::Maybe[Num]] => Mon::C::Try[Mon::C::Maybe[Num]]
|
43
|
+
def tryMaybeSquare(i)
|
44
|
+
i.bind { |j| j.bind { |k| k * k } }
|
45
|
+
end
|
46
|
+
|
47
|
+
Contract Mon::C::Try[Or[Num, String]] => Mon::C::Maybe[String]
|
48
|
+
def tryStringify(i)
|
49
|
+
Mon::M::Maybe[i.bind { |j| "We got: #{ j.to_s }" }._]
|
50
|
+
end
|
51
|
+
|
52
|
+
module Mon
|
53
|
+
|
54
|
+
module Contract
|
55
|
+
|
56
|
+
describe "List" do
|
57
|
+
|
58
|
+
it "should accept Lists" do
|
59
|
+
listSquare(Mon::M::List[1, 2, 3]).should eql Mon::M::List[1, 4, 9]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should accept empty Lists" do
|
63
|
+
listSquare(Mon::M::List[]).should eql Mon::M::List[]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be internal-type aware" do
|
67
|
+
expect {
|
68
|
+
listSquare(Mon::M::List["test"])
|
69
|
+
}.to raise_error(ContractError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "shouldn't accept any other types" do
|
73
|
+
expect {
|
74
|
+
listSquare([5])
|
75
|
+
}.to raise_error(ContractError)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Maybe" do
|
81
|
+
|
82
|
+
it "should accept Maybe monads" do
|
83
|
+
maybeSquare(Mon::M::Maybe[5]).should eql Mon::M::Maybe[25]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should accept empty Maybe monads" do
|
87
|
+
maybeSquare(Mon::M::None[]).should eql Mon::M::None[]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "shouldn't accept Maybe monads containing incorrect types" do
|
91
|
+
expect {
|
92
|
+
maybeSquare(Mon::M::Maybe["test"])
|
93
|
+
}.to raise_error(ContractError)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "shouldn't accept any other types" do
|
97
|
+
expect {
|
98
|
+
maybeSquare(5)
|
99
|
+
}.to raise_error(ContractError)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "Try" do
|
105
|
+
|
106
|
+
it "should accept Try monads" do
|
107
|
+
trySquare(Mon::M::Try[5]).should eql Mon::M::Success[25]
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should accept Failures as well as Successes" do
|
111
|
+
trySquare(Mon::M::Try.to { raise RuntimeError.new }).should be_a Mon::M::Failure
|
112
|
+
end
|
113
|
+
|
114
|
+
it "shouldn't accept Successes of the incorrect type" do
|
115
|
+
expect {
|
116
|
+
trySquare(Mon::M::Try["test"])
|
117
|
+
}.to raise_error(ContractError)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "shouldn't accept non-Try types" do
|
121
|
+
expect {
|
122
|
+
trySquare(5)
|
123
|
+
}.to raise_error(ContractError)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "shouldn't accept non-Try monadic types" do
|
127
|
+
expect {
|
128
|
+
trySquare(Mon::M::Maybe[5])
|
129
|
+
}.to raise_error(ContractError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "Lazy" do
|
134
|
+
|
135
|
+
it "should accept Lazy monads" do
|
136
|
+
lazySquare(Mon::M::Lazy[5]).should be_a Mon::M::Pending
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should accept Pending as well as Final" do
|
140
|
+
lazySquare(Mon::M::Lazy.eventually { sleep 1; 5 }).should be_a Mon::M::Lazy
|
141
|
+
end
|
142
|
+
|
143
|
+
it "shouldn't accept Finals of the incorrect type" do
|
144
|
+
expect {
|
145
|
+
lazySquare(Mon::M::Try["test"])
|
146
|
+
}.to raise_error(ContractError)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "shouldn't accept non-Lazy types" do
|
150
|
+
expect {
|
151
|
+
lazySquare(5)
|
152
|
+
}.to raise_error(ContractError)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "shouldn't accept non-Lazy monadic types" do
|
156
|
+
expect {
|
157
|
+
lazySquare(Mon::M::Try[5])
|
158
|
+
}.to raise_error(ContractError)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "Future" do
|
163
|
+
|
164
|
+
it "should accept Future monads" do
|
165
|
+
futureSquare(Mon::M::Future.eventually { 5 }).should eql Mon::M::Future[25]
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should accept Promise as well as Complete" do
|
169
|
+
futureSquare(Mon::M::Future.eventually { sleep 1; 5 }).should eql Mon::M::Future[25]
|
170
|
+
end
|
171
|
+
|
172
|
+
it "shouldn't accept Completes of the incorrect type" do
|
173
|
+
expect {
|
174
|
+
futureSquare(Mon::M::FutureComplete["test"])
|
175
|
+
}.to raise_error(ContractError)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "shouldn't accept non-Future types" do
|
179
|
+
expect {
|
180
|
+
futureSquare(5)
|
181
|
+
}.to raise_error(ContractError)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "shouldn't accept non-Future monadic types" do
|
185
|
+
expect {
|
186
|
+
futureSquare(Mon::M::Try[5])
|
187
|
+
}.to raise_error(ContractError)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "React" do
|
192
|
+
|
193
|
+
it "should accept Reactron monads" do
|
194
|
+
reactSquare(Mon::M::React[5]).should eql Mon::M::React[25]
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should accept Reactors as well as Reactrons" do
|
198
|
+
reactSquare(Mon::M::React[1].bind { |i| i * 5 }).should eql Mon::M::React[25]
|
199
|
+
end
|
200
|
+
|
201
|
+
it "shouldn't accept Reactrons of the incorrect type" do
|
202
|
+
expect {
|
203
|
+
reactSquare(Mon::M::React["test"])
|
204
|
+
}.to raise_error(ContractError)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "shouldn't accept Reactors of the incorrect type" do
|
208
|
+
expect {
|
209
|
+
reactSquare(Mon::M::React["tset"].bind { |s| s.reverse })
|
210
|
+
}.to raise_error(ContractError)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "shouldn't accept non-React types" do
|
214
|
+
expect {
|
215
|
+
reactSquare(5)
|
216
|
+
}.to raise_error(ContractError)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "shouldn't accept non-React monadic types" do
|
220
|
+
expect {
|
221
|
+
reactSquare(Mon::M::Try[5])
|
222
|
+
}.to raise_error(ContractError)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "Monad" do
|
227
|
+
|
228
|
+
it "should accept List" do
|
229
|
+
monadSquare(Mon::M::List[4, 5]).should eql Mon::M::List[16, 25]
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should accept Maybe" do
|
233
|
+
monadSquare(Mon::M::Maybe[5]).should eql Mon::M::Maybe[25]
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should accept None" do
|
237
|
+
monadSquare(Mon::M::None[]).should eql Mon::M::None[]
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should accept Try" do
|
241
|
+
monadSquare(Mon::M::Try.to { 5 }).should eql Mon::M::Success[25]
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should accept Failure" do
|
245
|
+
monadSquare(Mon::M::Try.to { throw RuntimeError.new }).should be_a Mon::M::Failure
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should accept Lazy" do
|
249
|
+
monadSquare(Mon::M::Lazy.eventually { 5 }).should be_a Mon::M::Pending
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should accept Final" do
|
253
|
+
monadSquare(Mon::M::Lazy[5]).should eql Mon::M::Final[25]
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should accept Future" do
|
257
|
+
monadSquare(Mon::M::Future.eventually { sleep 1; 5 }).should eql Mon::M::Future[25]
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should accept FutureComplete" do
|
261
|
+
monadSquare(Mon::M::Future[5]).should eql Mon::M::Future[25]
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should accept React" do
|
265
|
+
monadSquare(Mon::M::React[5]).should eql Mon::M::React[25]
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should accept Reactor" do
|
269
|
+
monadSquare(Mon::M::React[1].bind { |i| i * 5 }).should eql Mon::M::React[25]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe "Nesting" do
|
274
|
+
|
275
|
+
it "should nest correctly" do
|
276
|
+
tryMaybeSquare(Mon::M::Try[Mon::M::Maybe[5]]).should eql Mon::M::Try[Mon::M::Maybe[25]]
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should nest correctly with other types" do
|
280
|
+
tryStringify(Mon::M::Try[5]).should eql Mon::M::Maybe["We got: 5"]
|
281
|
+
tryStringify(Mon::M::Try["test"]).should eql Mon::M::Maybe["We got: test"]
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should catch improper types in nested values (monad)" do
|
285
|
+
expect {
|
286
|
+
tryMaybeSquare(Mon::M::Try[Mon::M::Maybe["test"]])
|
287
|
+
}.to raise_error(ContractError)
|
288
|
+
end
|
289
|
+
|
290
|
+
it "should catch improper types in nested values (non-monad)" do
|
291
|
+
expect {
|
292
|
+
tryStringify(Mon::M::Try[:some_symbol])
|
293
|
+
}.to raise_error(ContractError)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should catch massively wrong type errors 1" do
|
297
|
+
expect {
|
298
|
+
tryStringify("test")
|
299
|
+
}.to raise_error(ContractError)
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should catch massively wrong type errors 2" do
|
303
|
+
expect {
|
304
|
+
tryMaybeSquare("test")
|
305
|
+
}.to raise_error(ContractError)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|