monadic 0.4.0 → 0.5.0
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/CHANGELOG.md +11 -0
- data/Guardfile +3 -2
- data/README.md +28 -10
- data/examples/validation.rb +2 -2
- data/lib/monadic/either.rb +10 -9
- data/lib/monadic/maybe.rb +10 -5
- data/lib/monadic/monad.rb +1 -1
- data/lib/monadic/try.rb +10 -0
- data/lib/monadic/version.rb +1 -1
- data/lib/monadic.rb +1 -0
- data/spec/either_spec.rb +14 -9
- data/spec/examples/applicative_spec.rb +2 -2
- data/spec/examples/hotel_booking_spec.rb +133 -0
- data/spec/maybe_spec.rb +12 -2
- data/spec/try_spec.rb +36 -0
- data/spec/validation_spec.rb +3 -3
- metadata +9 -4
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.5.0
|
4
|
+
|
5
|
+
Add the `Try` helper which works similar to Either, but takes a block
|
6
|
+
|
7
|
+
Try { Date.parse('2012-02-30') } == Failure
|
8
|
+
Try { Date.parse('2012-02-28') } == Success
|
9
|
+
|
10
|
+
`Either` else now supports also a block
|
11
|
+
|
12
|
+
Failure(1).else {|other| 1 + 2 } == Failure(3)
|
13
|
+
|
3
14
|
## v0.4.0
|
4
15
|
`#or` returns for Nothing an alternative
|
5
16
|
|
data/Guardfile
CHANGED
@@ -8,6 +8,7 @@ end
|
|
8
8
|
|
9
9
|
guard 'rspec', :version => 2 do
|
10
10
|
watch(%r{^spec/.+_spec\.rb$})
|
11
|
-
watch(%r{^lib/(.+)\.rb$}) { |m| "spec
|
12
|
-
watch(
|
11
|
+
watch(%r{^lib/monadic/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^lib/monadic.rb$}) { "spec" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
13
14
|
end
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# Monadic
|
2
|
-
[](http://travis-ci.org/pzol/monadic)
|
2
|
+
[](http://travis-ci.org/pzol/monadic) [](http://coderwall.com/pzol)
|
3
3
|
|
4
4
|
helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in [Scala](http://www.scala-lang.org/) and [Haskell](http://www.haskell.org/) to my ruby projects.
|
5
5
|
|
6
|
-
My motivation to create this gem was that I often work with nested Hashes and need to reach deeply inside of them so my code is sprinkled with things like some_hash.fetch(:one, {}).fetch(:two, {}).fetch(:three, "unknown").
|
6
|
+
My motivation to create this gem was that I often work with nested Hashes and need to reach deeply inside of them so my code is sprinkled with things like some_hash.fetch(:one, {}).fetch(:two, {}).fetch(:three, "unknown").
|
7
7
|
|
8
8
|
We have the following monadics (monads, functors, applicatives and variations):
|
9
9
|
|
@@ -13,7 +13,7 @@ We have the following monadics (monads, functors, applicatives and variations):
|
|
13
13
|
|
14
14
|
What's the point of using monads in ruby? To me it started with having a safe way to deal with nil objects and other exceptions.
|
15
15
|
Thus you contain the erroneous behaviour within a monad - an indivisible, impenetrable unit. Functional programming considers _throwing_ exceptions to be a side-effect, instead we _propagate_ exceptions, i.e. return them as a result of a function call.
|
16
|
-
|
16
|
+
|
17
17
|
A monad is most effectively described as a computation that eventually returns a value. -- Wolfgang De Meuter
|
18
18
|
|
19
19
|
## Usage
|
@@ -50,7 +50,7 @@ Maybe(nil).empty? == true
|
|
50
50
|
Maybe(nil).truly? == false
|
51
51
|
|
52
52
|
# Just stays Just, unless you unbox it
|
53
|
-
Maybe('FOO').downcase == Just('foo')
|
53
|
+
Maybe('FOO').downcase == Just('foo')
|
54
54
|
Maybe('FOO').downcase.fetch == "foo" # unboxing the value
|
55
55
|
Maybe('FOO').downcase._ == "foo"
|
56
56
|
Maybe('foo').empty? == false # always non-empty
|
@@ -92,6 +92,7 @@ Maybe(1).to_s == '1'
|
|
92
92
|
`#or`
|
93
93
|
Maybe(nil).or(1) == 1
|
94
94
|
Maybe(1).or(2) == 1
|
95
|
+
Maybe(nil).or(nil) == Just(nil)
|
95
96
|
|
96
97
|
Falsey values (kind-of) examples:
|
97
98
|
|
@@ -214,6 +215,12 @@ Either(false == true).else('false was not true') == Failure(false was n
|
|
214
215
|
Success('truth needs no sugar coating').else('all lies') == Success('truth needs no sugar coating')
|
215
216
|
```
|
216
217
|
|
218
|
+
`Either#else` supports also a block
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
Failure(1).else {|other| 1 + 2 } == Failure(3)
|
222
|
+
```
|
223
|
+
|
217
224
|
Storing intermediate results in instance variables is possible, although it is not very elegant:
|
218
225
|
|
219
226
|
```ruby
|
@@ -224,9 +231,20 @@ result = Either.chain do
|
|
224
231
|
end
|
225
232
|
|
226
233
|
result == Success(101)
|
227
|
-
```
|
234
|
+
```
|
235
|
+
|
236
|
+
#### Try
|
237
|
+
|
238
|
+
`Try` helper which works similar to Either, but takes a block
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
Try { Date.parse('2012-02-30') } == Failure
|
242
|
+
Try { Date.parse('2012-02-28') } == Success
|
243
|
+
Try { Date.parse('2012-02-30') }.else {|e| "Exception: #{e.message}" } == Failure("Exception: invalid date")
|
244
|
+
```
|
245
|
+
|
228
246
|
|
229
|
-
### Validation
|
247
|
+
### Validation
|
230
248
|
The Validation applicative functor, takes a list of checks within a block. Each check must return either Success of Failure.
|
231
249
|
If Successful, it will return Success, if not a Failure monad, containing a list of failures.
|
232
250
|
Within the Failure() provide the reason why the check failed.
|
@@ -237,7 +255,7 @@ Example:
|
|
237
255
|
def validate(person)
|
238
256
|
check_age = ->(age_expr) {
|
239
257
|
age = age_expr.to_i
|
240
|
-
case
|
258
|
+
case
|
241
259
|
when age <= 0; Failure('Age must be > 0')
|
242
260
|
when age > 130; Failure('Age must be < 130')
|
243
261
|
else Success(age)
|
@@ -266,16 +284,16 @@ end
|
|
266
284
|
|
267
285
|
The above example, returns either `Success([32, :sober, :male])` or `Failure(['Age must be > 0', 'No drunks allowed'])` with a list of what went wrong during the validation.
|
268
286
|
|
269
|
-
See also [examples/validation.rb](https://github.com/pzol/monadic/blob/master/examples/validation.rb) and [examples/validation_module](https://github.com/pzol/monadic/blob/master/examples/validation_module.rb)
|
287
|
+
See also [examples/validation.rb](https://github.com/pzol/monadic/blob/master/examples/validation.rb) and [examples/validation_module](https://github.com/pzol/monadic/blob/master/examples/validation_module.rb)
|
270
288
|
|
271
|
-
### Monad
|
289
|
+
### Monad
|
272
290
|
All Monads include this module. Standalone it is an Identity monad. Not useful on its own. It's methods are usable on all its descendants.
|
273
291
|
|
274
292
|
__#map__ is used to map the inner value
|
275
293
|
|
276
294
|
```ruby
|
277
295
|
# minimum implementation of a monad
|
278
|
-
class Identity
|
296
|
+
class Identity
|
279
297
|
include Monadic::Monad
|
280
298
|
def self.unit(value)
|
281
299
|
new(value)
|
data/examples/validation.rb
CHANGED
@@ -5,7 +5,7 @@ class AgeTooHighError < ArgumentError; end
|
|
5
5
|
|
6
6
|
valid_age = ->(age_expr) {
|
7
7
|
age = age_expr.to_i
|
8
|
-
case
|
8
|
+
case
|
9
9
|
when age <= 0; Failure(AgeTooLowError.new(age_expr))
|
10
10
|
when age > 130; Failure(AgeTooHighError.new(age_expr))
|
11
11
|
else Success(age)
|
@@ -13,7 +13,7 @@ valid_age = ->(age_expr) {
|
|
13
13
|
}
|
14
14
|
|
15
15
|
valid_name = ->(name) {
|
16
|
-
case
|
16
|
+
case
|
17
17
|
when name =~ /\w{3,99}/i; Success(name)
|
18
18
|
else Failure('Invalid name')
|
19
19
|
end
|
data/lib/monadic/either.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Monadic
|
2
2
|
# @abstract Chains function calls and stops executing if one of them fails.
|
3
|
-
class Either
|
3
|
+
class Either
|
4
4
|
include Monadic::Monad
|
5
5
|
def self.chain(initial=nil, &block)
|
6
6
|
Either::Chain.new(&block).call(initial)
|
@@ -12,11 +12,11 @@ module Monadic
|
|
12
12
|
return Success.new(value)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Initialize is private, because it always would return an instance of Either, but Success or Failure
|
15
|
+
# Initialize is private, because it always would return an instance of Either, but Success or Failure
|
16
16
|
# are required (Either is abstract).
|
17
17
|
private_class_method :new
|
18
18
|
|
19
|
-
# Allows privileged access to the +Either+'s inner value from within a block.
|
19
|
+
# Allows privileged access to the +Either+'s inner value from within a block.
|
20
20
|
# This block should return a +Success+ or +Failure+ itself. It will be coerced into #Either
|
21
21
|
# @return [Success, Failure]
|
22
22
|
def bind(proc=nil, &block)
|
@@ -26,15 +26,16 @@ module Monadic
|
|
26
26
|
begin
|
27
27
|
Either(call(proc, block))
|
28
28
|
rescue StandardError => error
|
29
|
-
Failure(error)
|
29
|
+
Failure(error)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
alias :>= :bind
|
33
33
|
alias :+ :bind
|
34
34
|
|
35
35
|
# If it is a Failure it will return a new Failure with the provided value
|
36
|
-
# @return [Success, Failure]
|
37
|
-
def else(value)
|
36
|
+
# @return [Success, Failure]
|
37
|
+
def else(value=nil, &block)
|
38
|
+
return Failure(block.call(@value)) if failure? && block_given?
|
38
39
|
return Failure(value) if failure?
|
39
40
|
return self
|
40
41
|
end
|
@@ -53,7 +54,7 @@ module Monadic
|
|
53
54
|
is_a? Failure
|
54
55
|
end
|
55
56
|
|
56
|
-
private
|
57
|
+
private
|
57
58
|
def call(proc=nil, block)
|
58
59
|
func = (proc || block)
|
59
60
|
raise "No block or lambda given" unless func.is_a? Proc
|
@@ -99,7 +100,7 @@ module Monadic
|
|
99
100
|
module InstanceMethods
|
100
101
|
def initialize(value)
|
101
102
|
@value = join(value)
|
102
|
-
end
|
103
|
+
end
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
@@ -115,7 +116,7 @@ module Monadic
|
|
115
116
|
Failure.new(value)
|
116
117
|
end
|
117
118
|
|
118
|
-
# Factory method
|
119
|
+
# Factory method
|
119
120
|
# @return [Success]
|
120
121
|
def Success(value)
|
121
122
|
Success.new(value)
|
data/lib/monadic/maybe.rb
CHANGED
@@ -24,6 +24,7 @@ module Monadic
|
|
24
24
|
return self
|
25
25
|
end
|
26
26
|
|
27
|
+
# @return [true, false] true if the underlying value is true
|
27
28
|
def truly?
|
28
29
|
@value == true
|
29
30
|
end
|
@@ -33,6 +34,7 @@ module Monadic
|
|
33
34
|
class Just < Maybe
|
34
35
|
public_class_method :new
|
35
36
|
|
37
|
+
# @return the underlying value
|
36
38
|
def fetch(default=nil)
|
37
39
|
@value
|
38
40
|
end
|
@@ -42,13 +44,10 @@ module Monadic
|
|
42
44
|
Maybe(@value.__send__(m, *args))
|
43
45
|
end
|
44
46
|
|
47
|
+
# @return always self for Just
|
45
48
|
def or(other)
|
46
49
|
self
|
47
50
|
end
|
48
|
-
|
49
|
-
def to_s
|
50
|
-
@value.to_s
|
51
|
-
end
|
52
51
|
end
|
53
52
|
|
54
53
|
# Represents a NullObject
|
@@ -56,6 +55,7 @@ module Monadic
|
|
56
55
|
class << self
|
57
56
|
undef name
|
58
57
|
|
58
|
+
# @return the default value passed
|
59
59
|
def fetch(default=nil)
|
60
60
|
return self if default.nil?
|
61
61
|
return default
|
@@ -66,10 +66,15 @@ module Monadic
|
|
66
66
|
self
|
67
67
|
end
|
68
68
|
|
69
|
+
# @return an alternative value, the passed value is NOT coerced into Maybe, thus Nothing.or(nil) will be Just(nil)
|
69
70
|
def or(other)
|
70
|
-
|
71
|
+
Just.new(other)
|
71
72
|
end
|
72
73
|
|
74
|
+
# def respond_to?
|
75
|
+
|
76
|
+
# end
|
77
|
+
|
73
78
|
def to_ary
|
74
79
|
[]
|
75
80
|
end
|
data/lib/monadic/monad.rb
CHANGED
@@ -45,7 +45,7 @@ module Monadic
|
|
45
45
|
# Return the string representation of the Monad
|
46
46
|
def to_s
|
47
47
|
pretty_class_name = self.class.name.split('::')[-1]
|
48
|
-
"#{pretty_class_name}(#{
|
48
|
+
"#{pretty_class_name}(#{self.fetch.inspect})"
|
49
49
|
end
|
50
50
|
|
51
51
|
def ==(other)
|
data/lib/monadic/try.rb
ADDED
data/lib/monadic/version.rb
CHANGED
data/lib/monadic.rb
CHANGED
data/spec/either_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe Monadic::Either do
|
|
16
16
|
it 'Failure.new and Failure.unit and Failure() return the same' do
|
17
17
|
Failure(1).should == Failure.unit(1)
|
18
18
|
Failure.new(1).should == Failure.unit(1)
|
19
|
-
|
19
|
+
|
20
20
|
Failure(nil).should == Failure.unit(nil)
|
21
21
|
Failure.new(nil).should == Failure.unit(nil)
|
22
22
|
end
|
@@ -27,7 +27,7 @@ describe Monadic::Either do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it '#to_s works' do
|
30
|
-
Success.unit("it worked!").to_s.should ==
|
30
|
+
Success.unit("it worked!").to_s.should == 'Success("it worked!")'
|
31
31
|
Failure.unit(nil).to_s.should == "Failure(nil)"
|
32
32
|
end
|
33
33
|
|
@@ -56,15 +56,20 @@ describe Monadic::Either do
|
|
56
56
|
it '#else returns an alternative value considered Success if it is Nothing' do
|
57
57
|
Failure(false).else(true).should == Failure(true)
|
58
58
|
Either(nil).else(true).should == Failure(true)
|
59
|
+
Failure(1).else(nil).should == Failure(nil)
|
59
60
|
Success(true).else(false).should == Success(true)
|
60
61
|
Either(true).else(false).should == Success(true)
|
61
62
|
Success(false).else(true).should == Success(false)
|
62
63
|
end
|
63
64
|
|
65
|
+
it '#else with a block gets the original value passed' do
|
66
|
+
(Failure(1).else { |other| other + 1 }).should == Failure(2)
|
67
|
+
end
|
68
|
+
|
64
69
|
class User
|
65
70
|
attr :age, :gender, :sobriety
|
66
71
|
def self.find(id)
|
67
|
-
case id
|
72
|
+
case id
|
68
73
|
when -1; raise 'invalid user id'
|
69
74
|
when 0; nil
|
70
75
|
else User.new(id)
|
@@ -149,7 +154,7 @@ describe Monadic::Either do
|
|
149
154
|
|
150
155
|
it 'passes the result value from the previous call to the next' do
|
151
156
|
either = Success(1).
|
152
|
-
>= {|prev| Success(prev + 1) }. # a block
|
157
|
+
>= {|prev| Success(prev + 1) }. # a block
|
153
158
|
>= -> prev { Success(prev + 100) } # lambda/proc
|
154
159
|
|
155
160
|
either.should == Success(102)
|
@@ -160,7 +165,7 @@ describe Monadic::Either do
|
|
160
165
|
end
|
161
166
|
|
162
167
|
it 'allows you to use parameterless lambdas (#arity == 0)' do
|
163
|
-
(Success(0) >=
|
168
|
+
(Success(0) >=
|
164
169
|
-> { Success(1) }
|
165
170
|
).should == Success(1)
|
166
171
|
end
|
@@ -198,12 +203,12 @@ describe Monadic::Either do
|
|
198
203
|
end
|
199
204
|
|
200
205
|
it 'supports Either.chain' do
|
201
|
-
Either.chain do
|
206
|
+
Either.chain do
|
202
207
|
bind -> { Success(1) }
|
203
208
|
bind -> { Success(2) }
|
204
209
|
end.should == Success(2)
|
205
210
|
|
206
|
-
Either.chain do
|
211
|
+
Either.chain do
|
207
212
|
bind -> { Success(1) }
|
208
213
|
bind ->(p) { Success(p + 1) }
|
209
214
|
end.should == Success(2)
|
@@ -215,9 +220,9 @@ describe Monadic::Either do
|
|
215
220
|
|
216
221
|
it 'README example' do
|
217
222
|
params = { :path => 'foo' }
|
218
|
-
def load_file(path);
|
223
|
+
def load_file(path);
|
219
224
|
fail "invalid path" unless path
|
220
|
-
Success("bar");
|
225
|
+
Success("bar");
|
221
226
|
end
|
222
227
|
def process_content(content); content.start_with?('b') ? Success(content.upcase) : Failure('invalid content'); end
|
223
228
|
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support/time'
|
3
|
+
|
4
|
+
describe 'Hotel Booking Example' do
|
5
|
+
|
6
|
+
class Request < Struct
|
7
|
+
def self.create(params, params_module)
|
8
|
+
value = self.create_value(params || {}, params_module)
|
9
|
+
|
10
|
+
if value.values.all?(&:success?)
|
11
|
+
Success(value)
|
12
|
+
else
|
13
|
+
Failure(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
Hash[*members.zip(values).flatten]
|
19
|
+
end
|
20
|
+
|
21
|
+
def unwrap
|
22
|
+
Struct.new(*members).new(*values.map(&:fetch))
|
23
|
+
# self.class.new(*values.map(&:fetch))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def self.create_value(params, params_module)
|
28
|
+
properties = params_module.methods - Module.methods
|
29
|
+
request_klas = self.new(*properties)
|
30
|
+
properties.reduce(request_klas.new) do |request, property|
|
31
|
+
request[property] = params_module.send(property, params)
|
32
|
+
request
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module HotelBookingRequestParams
|
38
|
+
extend self
|
39
|
+
|
40
|
+
def hotel_code(params)
|
41
|
+
value = params['hotel_code']
|
42
|
+
case
|
43
|
+
when value.nil?
|
44
|
+
Failure('hotel_code must not be empty')
|
45
|
+
when not(value =~ /^[A-Z]{3}[A-Z0-9]{4}$/)
|
46
|
+
Failure('hotel_code must be of pattern XXX0001')
|
47
|
+
else
|
48
|
+
Success(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def nights(params)
|
53
|
+
value = params.fetch('nights', 0).to_i
|
54
|
+
if value <= 0
|
55
|
+
Failure("nights must be a number greater than 0 (got '#{params['nights']}')")
|
56
|
+
else
|
57
|
+
Success(value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
VALID_ROOM_TYPE = %w[SR DR TR QR]
|
62
|
+
def room_type(params)
|
63
|
+
case value = params['room_type']
|
64
|
+
when nil
|
65
|
+
Failure('room_type must not be empty')
|
66
|
+
when is_valid_room_type
|
67
|
+
Success(value)
|
68
|
+
else
|
69
|
+
Failure("room_type '#{value}' must be one of #{VALID_ROOM_TYPE.join(', ')}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_in(params)
|
74
|
+
param = params['check_in']
|
75
|
+
return Failure("check_in must not be empty") if param.nil?
|
76
|
+
return Try { Date.parse(param) }.else {|e| "check_in #{e.message} '#{param}'" }
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def is_valid_room_type
|
81
|
+
Object.new.tap {|matcher|
|
82
|
+
def matcher.===(value)
|
83
|
+
VALID_ROOM_TYPE.include? value
|
84
|
+
end
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
let(:params_proto) do
|
90
|
+
{'hotel_code' => 'STO0001',
|
91
|
+
'check_in' => '2012-06-15',
|
92
|
+
'nights' => '3',
|
93
|
+
'room_type' => 'DR',
|
94
|
+
'guest1' => 'Max Payne',
|
95
|
+
'guest2' => 'Jenny Payne'}
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'builds a valid request' do
|
99
|
+
result = Request.create(params_proto, HotelBookingRequestParams)
|
100
|
+
result.should be_a Success
|
101
|
+
request = result.fetch.unwrap
|
102
|
+
request.hotel_code.should eq "STO0001"
|
103
|
+
request.nights.should eq 3
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'reports an invalid room_type' do
|
107
|
+
params = params_proto.merge 'room_type' => 'XX'
|
108
|
+
result = Request.create(params, HotelBookingRequestParams)
|
109
|
+
result.should be_a Failure
|
110
|
+
request = result.fetch
|
111
|
+
request.room_type.should be_a Failure
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'reports an invalid check_in time' do
|
115
|
+
params = params_proto.merge 'check_in' => '2012-11-31'
|
116
|
+
result = Request.create(params, HotelBookingRequestParams)
|
117
|
+
result.should be_a Failure
|
118
|
+
request = result.fetch
|
119
|
+
request.check_in.should be_a Failure
|
120
|
+
request.check_in.should == Failure("check_in invalid date '2012-11-31'")
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'builds a failure request with no params' do
|
124
|
+
result = Request.create(nil, HotelBookingRequestParams)
|
125
|
+
result.should be_a Failure
|
126
|
+
request = result.fetch
|
127
|
+
request.hotel_code.should be_a Failure
|
128
|
+
request.nights.should be_a Failure
|
129
|
+
request.room_type.should be_a Failure
|
130
|
+
request.check_in.should be_a Failure
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
data/spec/maybe_spec.rb
CHANGED
@@ -69,11 +69,21 @@ describe Monadic::Maybe do
|
|
69
69
|
end
|
70
70
|
|
71
71
|
it 'Just#to_s is "value"' do
|
72
|
-
Just.unit(123).to_s.should == "123"
|
72
|
+
Just.unit(123).to_s.should == "Just(123)"
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'Just#or return self' do
|
76
|
-
Maybe(1).or(2).should
|
76
|
+
Maybe(1).or(2).should == Just(1)
|
77
|
+
Maybe(nil).or(nil).should == Just.new(nil)
|
78
|
+
Maybe(nil).something.or('').fetch.should == ''
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'Just#inspect returns Just(value)' do
|
82
|
+
Just(1).inspect.should == 'Just(1)'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'works with method_missing !caution! with Monad.methods like #join' do
|
86
|
+
Maybe([1, 2]).join(' ').should == ' '
|
77
87
|
end
|
78
88
|
|
79
89
|
end
|
data/spec/try_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Monadic::Try' do
|
4
|
+
it 'catches exceptions' do
|
5
|
+
Try { Date.parse('2012-02-30') }.should be_a Failure
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'returns Failure when the value is nil or false' do
|
9
|
+
Try { nil }.should be_a Failure
|
10
|
+
Try { false }.should be_a Failure
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns Failure for an empty collection' do
|
14
|
+
Try { [] }.should be_a Failure
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'without a block it works like Either' do
|
18
|
+
Try(true).should be_a Success
|
19
|
+
Try(false).should be_a Failure
|
20
|
+
Try(nil).should be_a Failure
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'with a proc' do
|
24
|
+
pending
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns Success for non-nil values' do
|
28
|
+
Try { 1 }.should == Success(1)
|
29
|
+
Try { "string" }.should == Success("string")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'combined with else and a block' do
|
33
|
+
Try { Date.parse('2012-02-30') }.else {|e| "Exception: #{e.message}" }.should == Failure("Exception: invalid date")
|
34
|
+
Try { Date.parse('2012-02-28') }.else {|e| "Exception: #{e.message}" }.should == Success(Date.parse('2012-02-28'))
|
35
|
+
end
|
36
|
+
end
|
data/spec/validation_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe Monadic::Validation do
|
|
8
8
|
def validate(person)
|
9
9
|
check_age = ->(age_expr) {
|
10
10
|
age = age_expr.to_i
|
11
|
-
case
|
11
|
+
case
|
12
12
|
when age <= 0; Failure('Age must be > 0')
|
13
13
|
when age > 130; Failure('Age must be < 130')
|
14
14
|
else Success(age)
|
@@ -20,13 +20,13 @@ describe Monadic::Validation do
|
|
20
20
|
when :sober, :tipsy; Success(sobriety)
|
21
21
|
when :drunk ; Failure('No drunks allowed')
|
22
22
|
else Failure("Sobriety state '#{sobriety}' is not allowed")
|
23
|
-
end
|
23
|
+
end
|
24
24
|
}
|
25
25
|
|
26
26
|
check_gender = ->(gender) {
|
27
27
|
gender == :male || gender == :female ? Success(gender) : Failure("Invalid gender #{gender}")
|
28
28
|
}
|
29
|
-
|
29
|
+
|
30
30
|
Validation() do
|
31
31
|
check { check_age.(person.age); }
|
32
32
|
check { check_sobriety.(person.sobriety) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monadic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -147,17 +147,20 @@ files:
|
|
147
147
|
- lib/monadic/errors.rb
|
148
148
|
- lib/monadic/maybe.rb
|
149
149
|
- lib/monadic/monad.rb
|
150
|
+
- lib/monadic/try.rb
|
150
151
|
- lib/monadic/validation.rb
|
151
152
|
- lib/monadic/version.rb
|
152
153
|
- monadic.gemspec
|
153
154
|
- spec/core_ext/object_spec.rb
|
154
155
|
- spec/either_spec.rb
|
155
156
|
- spec/examples/applicative_spec.rb
|
157
|
+
- spec/examples/hotel_booking_spec.rb
|
156
158
|
- spec/jruby_fixes.rb
|
157
159
|
- spec/maybe_spec.rb
|
158
160
|
- spec/monad_axioms.rb
|
159
161
|
- spec/monad_spec.rb
|
160
162
|
- spec/spec_helper.rb
|
163
|
+
- spec/try_spec.rb
|
161
164
|
- spec/validation_spec.rb
|
162
165
|
homepage: http://github.com/pzol/monadic
|
163
166
|
licenses: []
|
@@ -173,7 +176,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
173
176
|
version: '0'
|
174
177
|
segments:
|
175
178
|
- 0
|
176
|
-
hash:
|
179
|
+
hash: 44748107420070269
|
177
180
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
181
|
none: false
|
179
182
|
requirements:
|
@@ -182,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
185
|
version: '0'
|
183
186
|
segments:
|
184
187
|
- 0
|
185
|
-
hash:
|
188
|
+
hash: 44748107420070269
|
186
189
|
requirements: []
|
187
190
|
rubyforge_project:
|
188
191
|
rubygems_version: 1.8.24
|
@@ -193,9 +196,11 @@ test_files:
|
|
193
196
|
- spec/core_ext/object_spec.rb
|
194
197
|
- spec/either_spec.rb
|
195
198
|
- spec/examples/applicative_spec.rb
|
199
|
+
- spec/examples/hotel_booking_spec.rb
|
196
200
|
- spec/jruby_fixes.rb
|
197
201
|
- spec/maybe_spec.rb
|
198
202
|
- spec/monad_axioms.rb
|
199
203
|
- spec/monad_spec.rb
|
200
204
|
- spec/spec_helper.rb
|
205
|
+
- spec/try_spec.rb
|
201
206
|
- spec/validation_spec.rb
|