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/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
|