mon 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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/list.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# The List monad wraps lists. Gasp!
|
2
|
+
#
|
3
|
+
# A simple example:
|
4
|
+
# <tt>l = List[1, 2, 3]
|
5
|
+
# list9 = l.bind { |i| i * 9 } # ==> List[9, 18, 27]
|
6
|
+
# longList = l.bind { |i| [i, i * 2] } # ==> List[1, 2, 2, 4, 3, 6]</tt>
|
7
|
+
#
|
8
|
+
# Simple and straightforward.
|
9
|
+
|
10
|
+
module Mon
|
11
|
+
|
12
|
+
module Monad
|
13
|
+
|
14
|
+
require_relative 'chainable_monad'
|
15
|
+
require_relative 'monad'
|
16
|
+
|
17
|
+
class List < Monad
|
18
|
+
|
19
|
+
include ChainableMonad
|
20
|
+
|
21
|
+
def initialize(list)
|
22
|
+
@list = list
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create a list. Eg: List[1, 2, 3]
|
26
|
+
def self.[](*list)
|
27
|
+
List.new(list)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Apply fun to all elements of the list (ala map):
|
31
|
+
# List[1, 2, 3].map { |i| i + 5 } # ==> List[6, 7, 8]
|
32
|
+
def bind &fun
|
33
|
+
List.send(:new, @list.map { |i| fun.call(i) }.map { |i| (i.is_a? List) ? i.to_a : i}.flatten(1))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the array wrapped by the List
|
37
|
+
def unwrap
|
38
|
+
to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
# Alias for #unwrap
|
42
|
+
def _
|
43
|
+
to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
def _canBind? name
|
47
|
+
@list.all? { |i| i.respond_to?(name) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
(@list.length > 3) ? (tail = "...") : tail = ""
|
52
|
+
"List[#{ @list.take(3).join(", ") }#{ tail }]"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return the array wrapped by the list
|
56
|
+
def to_a
|
57
|
+
@list
|
58
|
+
end
|
59
|
+
|
60
|
+
class << self
|
61
|
+
protected :new
|
62
|
+
end
|
63
|
+
|
64
|
+
def eql?(other)
|
65
|
+
if other.is_a? List
|
66
|
+
@list == other.unwrap
|
67
|
+
elsif other.is_a? Array
|
68
|
+
@list == other
|
69
|
+
else
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(o)
|
75
|
+
eql?(o)
|
76
|
+
end
|
77
|
+
|
78
|
+
def equal?(o)
|
79
|
+
eql?(o)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self::valid?(o)
|
83
|
+
o.is_a?(List)
|
84
|
+
end
|
85
|
+
|
86
|
+
protected :unwrap, :_canBind?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/monads/maybe.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# The Maybe monad wraps a possible value. It allows chaining of
|
2
|
+
# operations on a potentially-null value.
|
3
|
+
#
|
4
|
+
# A simple example:
|
5
|
+
# <tt>m = Maybe[callSomeIntOrNilFunction()]
|
6
|
+
# v = (m * 3 + 2).abs
|
7
|
+
# # Depending whether the original method call returned a number or nil,
|
8
|
+
# # could be either None or Some[N].</tt>
|
9
|
+
|
10
|
+
module Mon
|
11
|
+
|
12
|
+
module Monad
|
13
|
+
|
14
|
+
require_relative 'chainable_monad'
|
15
|
+
require_relative 'monad'
|
16
|
+
|
17
|
+
# Superclass for Some and None. Can be used as follows:
|
18
|
+
# <tt>m = Maybe[nil] # ==> None
|
19
|
+
# m = Maybe[5] # ==> Some[5]
|
20
|
+
# m = Maybe[nil] * 7 # ==> None
|
21
|
+
# m = Maybe[5] * 7 # ==> Some[35]
|
22
|
+
# m = Maybe[call_to_fun].someOperation(3) # ==> Some[...] or None, never an error</tt>
|
23
|
+
class Maybe < Monad
|
24
|
+
include ChainableMonad
|
25
|
+
|
26
|
+
# Override to catch None
|
27
|
+
def method_missing(name, *args, &fun)
|
28
|
+
self.bind { |o| o.send(name, *args, &fun) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Use to instantiate a Maybe monad:
|
32
|
+
# <tt>m = Maybe[<either nil/false or not>]</tt>
|
33
|
+
def self.[](obj)
|
34
|
+
if obj.is_a? Maybe
|
35
|
+
obj
|
36
|
+
elsif obj
|
37
|
+
Some[obj]
|
38
|
+
else
|
39
|
+
None[]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the value, or throw an exception (using the optional supplied msg) if it's empty
|
44
|
+
def orFail(msg = nil)
|
45
|
+
msg = "#{ self } is empty!" unless msg
|
46
|
+
throw RuntimeError.new(msg)
|
47
|
+
end
|
48
|
+
|
49
|
+
# For Contracts, DEPRECATED
|
50
|
+
def valid?(o)
|
51
|
+
o.is_a? Maybe
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
protected :new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# The Some class represents a value that is NOT null/false
|
60
|
+
class Some < Maybe
|
61
|
+
|
62
|
+
def initialize(obj)
|
63
|
+
@obj = obj
|
64
|
+
super()
|
65
|
+
end
|
66
|
+
|
67
|
+
# Wrap an object. You probably want Maybe[...], but there's a slight difference:
|
68
|
+
# Maybe[nil] # ==> None
|
69
|
+
# Some[nil] # ==> Some[nil]
|
70
|
+
def self.[](obj)
|
71
|
+
if obj.is_a? None
|
72
|
+
obj
|
73
|
+
else
|
74
|
+
Some.new(obj)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Unwrap the wrapped value, nil or not
|
79
|
+
def unwrap
|
80
|
+
@obj
|
81
|
+
end
|
82
|
+
|
83
|
+
def _canBind? name
|
84
|
+
@obj.respond_to? name
|
85
|
+
end
|
86
|
+
|
87
|
+
# If there's a wrapped value, return it. Otherwise, either return
|
88
|
+
# the supplied object, or execute the supplied block.
|
89
|
+
# Eg:
|
90
|
+
# <tt>Maybe[1].or(5) # ==> 1
|
91
|
+
# Maybe[nil].or(5) # ==> 5
|
92
|
+
# Maybe[nil].or { throw Exception.new("...") } # ==> Exception!
|
93
|
+
def or obj = nil, &f
|
94
|
+
@obj
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the value, or throw an exception (using the optional supplied msg) if it's empty
|
98
|
+
def orFail(msg = nil)
|
99
|
+
msg = "No such value!" if msg.nil?
|
100
|
+
self::or { throw RuntimeError.new(msg) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# If we're wrapping a value, apply fun() to it. Otherwise, None stays None.
|
104
|
+
def bind &fun
|
105
|
+
o = fun.call(@obj)
|
106
|
+
if o.is_a? Maybe
|
107
|
+
o
|
108
|
+
elsif o
|
109
|
+
Some[o]
|
110
|
+
else
|
111
|
+
None::none
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_s
|
116
|
+
"Some[#{ @obj.to_s }]"
|
117
|
+
end
|
118
|
+
|
119
|
+
def eql?(other)
|
120
|
+
if other.is_a? Some
|
121
|
+
@obj == other.unwrap
|
122
|
+
else
|
123
|
+
@obj == other
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def ==(o)
|
128
|
+
eql?(o)
|
129
|
+
end
|
130
|
+
|
131
|
+
def equal?(o)
|
132
|
+
eql?(o)
|
133
|
+
end
|
134
|
+
|
135
|
+
class << self
|
136
|
+
protected :new
|
137
|
+
end
|
138
|
+
|
139
|
+
protected :unwrap, :_canBind?
|
140
|
+
end
|
141
|
+
|
142
|
+
class None < Maybe
|
143
|
+
|
144
|
+
# If we're wrapping a value, apply fun() to it. Otherwise, None stays None.
|
145
|
+
def bind &fun
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# Unwrap the wrapped value, nil or not
|
150
|
+
def unwrap
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# If there's a wrapped value, return it. Otherwise, either return
|
155
|
+
# the supplied object, or execute the supplied block.
|
156
|
+
# Eg:
|
157
|
+
# <tt>Maybe[1].or(5) # ==> 1
|
158
|
+
# Maybe[nil].or(5) # ==> 5
|
159
|
+
# Maybe[nil].or { throw Exception.new("...") } # ==> Exception!</tt>
|
160
|
+
def or obj = nil, &f
|
161
|
+
if obj and f
|
162
|
+
f.call obj
|
163
|
+
elsif f
|
164
|
+
f.call
|
165
|
+
else
|
166
|
+
obj
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def _canBind? name
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return a new None object. Takes no args.
|
175
|
+
# <tt>None[] # ==> None</tt>
|
176
|
+
def self.[]
|
177
|
+
None.new
|
178
|
+
end
|
179
|
+
|
180
|
+
def to_s
|
181
|
+
"None"
|
182
|
+
end
|
183
|
+
|
184
|
+
def initialize
|
185
|
+
super()
|
186
|
+
end
|
187
|
+
|
188
|
+
def eql?(other)
|
189
|
+
other.is_a? None
|
190
|
+
end
|
191
|
+
|
192
|
+
def ==(o)
|
193
|
+
eql?(o)
|
194
|
+
end
|
195
|
+
|
196
|
+
def equal?(o)
|
197
|
+
eql?(o)
|
198
|
+
end
|
199
|
+
|
200
|
+
def self::none
|
201
|
+
self.new
|
202
|
+
end
|
203
|
+
|
204
|
+
class << self
|
205
|
+
protected :new
|
206
|
+
end
|
207
|
+
|
208
|
+
protected :unwrap, :_canBind?
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
data/lib/monads/monad.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# The React monad wraps a changeable value, and catches changes to that value.
|
2
|
+
#
|
3
|
+
# A simple example:
|
4
|
+
# <tt>r = React[5]
|
5
|
+
# d = r * r
|
6
|
+
# # Currently r = Reactron[5], d = Reactor[25]
|
7
|
+
# r << 25
|
8
|
+
# # Now r = Reactron[25], d = Reactor[625]
|
9
|
+
# dd = d * 2
|
10
|
+
# # d = Reactor[625], dd = Reactor[1250]</tt>
|
11
|
+
#
|
12
|
+
# More or less, this is a Listener pattern. Reactrons are listenable,
|
13
|
+
# Reactors are listeners.
|
14
|
+
|
15
|
+
module Mon
|
16
|
+
|
17
|
+
module Monad
|
18
|
+
|
19
|
+
require_relative 'chainable_monad'
|
20
|
+
require_relative 'monad'
|
21
|
+
|
22
|
+
# The React class is the parent of Reactron and Reactor. The key difference
|
23
|
+
# between the two is the presence of the << operator. The value of a Reactor is
|
24
|
+
# derived from a Reactron, and cannot be directly changed. If viewed as a tree,
|
25
|
+
# Reactrons are the root, and only the root may be changed.
|
26
|
+
#
|
27
|
+
# However, we can have more than one root!
|
28
|
+
# <tt>m = React[2] # ==> Reactron[2]
|
29
|
+
# n = React[10] # ==> Reactron[10]
|
30
|
+
# v = m * n # Currently v == Reactor[20]
|
31
|
+
# n << 5 # Now v == Reactor[10]</tt>
|
32
|
+
#
|
33
|
+
# Usage of React is straightforward:
|
34
|
+
# <tt>r = React[some_value]</tt>
|
35
|
+
class React < Monad
|
36
|
+
include ChainableMonad
|
37
|
+
|
38
|
+
# Wrap a value in a Reactron:
|
39
|
+
# <tt>r = React["test"] # ==> Reactron["test"]
|
40
|
+
def self::[] obj
|
41
|
+
if (obj.is_a? Proc)
|
42
|
+
Reactor[obj]
|
43
|
+
else
|
44
|
+
Reactron[obj]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
protected :new
|
50
|
+
end
|
51
|
+
|
52
|
+
def self::valid?(v)
|
53
|
+
v.is_a? React
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# The Reactron class represents a changeable value, from which other
|
58
|
+
# values can be derived (as Reactrons)
|
59
|
+
class Reactron < React
|
60
|
+
|
61
|
+
def initialize(obj)
|
62
|
+
@obj = obj
|
63
|
+
end
|
64
|
+
|
65
|
+
# You should be using React[...]
|
66
|
+
def self::[] obj
|
67
|
+
Reactron.new(obj)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Apply fun to the value wrapped by this Reactron. Returns a
|
71
|
+
# Reactor. Whenever the value is changed (with <<), all derived
|
72
|
+
# Reactors are also updated.
|
73
|
+
def bind &fun
|
74
|
+
Reactor[lambda { fun.call(self.unwrap) }]
|
75
|
+
end
|
76
|
+
|
77
|
+
def _canBind? name
|
78
|
+
@obj.respond_to? name
|
79
|
+
end
|
80
|
+
|
81
|
+
# Unwrap the value contained by this Reactron
|
82
|
+
def unwrap
|
83
|
+
@obj
|
84
|
+
end
|
85
|
+
|
86
|
+
# Change the value of this Reactron
|
87
|
+
def << obj
|
88
|
+
@obj = obj
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
def eql? o
|
93
|
+
if o.is_a? React
|
94
|
+
@obj == o.unwrap
|
95
|
+
else
|
96
|
+
@obj == o
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def equal? o
|
101
|
+
eql? o
|
102
|
+
end
|
103
|
+
|
104
|
+
def ==(o)
|
105
|
+
eql? o
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
"Reactron[#{ @obj }]"
|
110
|
+
end
|
111
|
+
|
112
|
+
class << self
|
113
|
+
protected :new
|
114
|
+
end
|
115
|
+
|
116
|
+
protected :_canBind?
|
117
|
+
end
|
118
|
+
|
119
|
+
class Reactor < React
|
120
|
+
|
121
|
+
def initialize(fun)
|
122
|
+
@fun = fun
|
123
|
+
end
|
124
|
+
|
125
|
+
# Apply fun to the value wrapped by this Reactor (which in turn is
|
126
|
+
# a transform on some Reactron), returning another
|
127
|
+
# Reactron. Changes to the root Reactron will propagate through
|
128
|
+
# the whole tree.
|
129
|
+
def bind &fun
|
130
|
+
Reactor[lambda { fun.call(self.unwrap) }]
|
131
|
+
end
|
132
|
+
|
133
|
+
# You want React[...]
|
134
|
+
def self::[] fun
|
135
|
+
Reactor.new(fun)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Unwrap the (current) value contained by this Reactor
|
139
|
+
def unwrap
|
140
|
+
r = @fun.call
|
141
|
+
r.is_a?(Reactor) ? r.unwrap : r
|
142
|
+
end
|
143
|
+
|
144
|
+
def _canBind? name
|
145
|
+
unwrap.respond_to? name
|
146
|
+
end
|
147
|
+
|
148
|
+
def eql? o
|
149
|
+
if o.is_a? React
|
150
|
+
@fun.call == o.unwrap
|
151
|
+
else
|
152
|
+
@fun.call == o
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def equal? o
|
157
|
+
eql? o
|
158
|
+
end
|
159
|
+
|
160
|
+
def ==(o)
|
161
|
+
eql? o
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_s
|
165
|
+
"Reactor[#{ unwrap }]"
|
166
|
+
end
|
167
|
+
|
168
|
+
class << self
|
169
|
+
protected :new
|
170
|
+
end
|
171
|
+
|
172
|
+
protected :_canBind?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|