monadic 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +12 -1
- data/README.md +166 -134
- data/examples/validation.rb +9 -3
- data/examples/validation_module.rb +46 -0
- data/lib/monadic/either.rb +4 -3
- data/lib/monadic/maybe.rb +3 -1
- data/lib/monadic/monad.rb +2 -6
- data/lib/monadic/version.rb +1 -1
- data/spec/either_spec.rb +1 -1
- data/spec/monad_spec.rb +19 -12
- data/spec/spec_helper.rb +1 -1
- metadata +3 -2
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.1.1
|
4
|
+
|
5
|
+
`Either()` coerces only `StandardError` to `Failure`, other exceptions higher in the hierarchy are will break the flow.
|
6
|
+
Thanks to @pithyless for the suggestion.
|
7
|
+
|
8
|
+
Monad is now a Module instead of a Class as previously. This fits the Monad metaphor better. Each monad must now implement the unit method itself, which is the correct way to do anyway.
|
9
|
+
|
10
|
+
|
3
11
|
## v0.1.0
|
4
12
|
|
5
13
|
You can now chain Eithers with `+` without providing a block or a proc:
|
@@ -12,7 +20,10 @@ All monads now have the `#to_ary` and `#to_a` method, which coerces the inner va
|
|
12
20
|
I am considering the Api now almost stable, the only thing I am not so sure about,
|
13
21
|
is whether to apply the magic coercion or not.
|
14
22
|
|
15
|
-
`Validation()` now returns the successfully validated values.
|
23
|
+
`Validation()` now returns the successfully validated values.
|
24
|
+
See [examples/validation.rb](https://github.com/pzol/monadic/blob/master/examples/validation.rb)
|
25
|
+
and [examples/validation_module](https://github.com/pzol/monadic/blob/master/examples/validation_module.rb)
|
26
|
+
|
16
27
|
|
17
28
|
## v0.0.7
|
18
29
|
|
data/README.md
CHANGED
@@ -23,61 +23,72 @@ Most people probably will be interested in the Maybe monad, as it solves the pro
|
|
23
23
|
|
24
24
|
Maybe is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Maybe, stays in the Maybe'.
|
25
25
|
|
26
|
-
Maybe(User.find(123)).name._ # ._ is a shortcut for .fetch
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
```ruby
|
28
|
+
Maybe(User.find(123)).name._ # ._ is a shortcut for .fetch
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
# if you prefer the alias Maybe instead of option
|
31
|
+
Maybe(User.find(123)).name._
|
32
|
+
|
33
|
+
# confidently diving into nested hashes
|
34
|
+
Maybe({})[:a][:b][:c] == Nothing
|
35
|
+
Maybe({})[:a][:b][:c].fetch('unknown') == "unknown"
|
36
|
+
Maybe(a: 1)[:a]._ == 1
|
37
|
+
```
|
35
38
|
|
36
39
|
Basic usage examples:
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
41
|
+
```ruby
|
42
|
+
# handling nil (None serves as NullObject)
|
43
|
+
Maybe(nil).a.b.c == Nothing
|
44
|
+
|
45
|
+
# Nothing
|
46
|
+
Maybe(nil)._ == Nothing
|
47
|
+
"#{Maybe(nil)}" == "Nothing"
|
48
|
+
Maybe(nil)._("unknown") == "unknown"
|
49
|
+
Maybe(nil).empty? == true
|
50
|
+
Maybe(nil).truly? == false
|
51
|
+
|
52
|
+
# Just stays Just, unless you unbox it
|
53
|
+
Maybe('FOO').downcase == Just('foo')
|
54
|
+
Maybe('FOO').downcase.fetch == "foo" # unboxing the value
|
55
|
+
Maybe('FOO').downcase._ == "foo"
|
56
|
+
Maybe('foo').empty? == false # always non-empty
|
57
|
+
Maybe('foo').truly? == true # depends on the boxed value
|
58
|
+
Maybe(false).empty? == false
|
59
|
+
Maybe(false).truly? == false
|
60
|
+
```
|
56
61
|
|
57
62
|
Map, select:
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
64
|
+
```ruby
|
65
|
+
Maybe(123).map { |value| User.find(value) } == Just(someUser) # if user found
|
66
|
+
Maybe(0).map { |value| User.find(value) } == Nothing # if user not found
|
67
|
+
Maybe([1,2]).map { |value| value.to_s } == Just(["1", "2"]) # for all Enumerables
|
62
68
|
|
63
|
-
|
64
|
-
|
69
|
+
Maybe('foo').select { |value| value.start_with?('f') } == Just('foo')
|
70
|
+
Maybe('bar').select { |value| value.start_with?('f') } == Nothing
|
71
|
+
```
|
65
72
|
|
66
73
|
Treat it like an array:
|
67
74
|
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
```ruby
|
76
|
+
Maybe(123).to_a == [123]
|
77
|
+
Maybe([123, 456]).to_a == [123, 456]
|
78
|
+
Maybe(nil).to_a == []
|
79
|
+
```
|
71
80
|
|
72
81
|
Falsey values (kind-of) examples:
|
73
82
|
|
74
|
-
|
75
|
-
|
83
|
+
```ruby
|
84
|
+
user = Maybe(User.find(123))
|
85
|
+
user.name._
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
87
|
+
user.subscribed? # always true
|
88
|
+
user.subscribed?.truly? # true if subscribed is true
|
89
|
+
user.subscribed?.fetch(false) # same as above
|
90
|
+
user.subscribed?.or(false) # same as above
|
91
|
+
```
|
81
92
|
|
82
93
|
Remember! a Maybe is never false (in Ruby terms), if you want to know if it is false, call `#empty?` of `#truly?`
|
83
94
|
|
@@ -85,32 +96,36 @@ Remember! a Maybe is never false (in Ruby terms), if you want to know if it is f
|
|
85
96
|
|
86
97
|
Slug example
|
87
98
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
99
|
+
```ruby
|
100
|
+
# instead of
|
101
|
+
def slug(title)
|
102
|
+
if title
|
103
|
+
title.strip.downcase.tr_s('^[a-z0-9]', '-')
|
104
|
+
end
|
105
|
+
end
|
94
106
|
|
95
|
-
|
107
|
+
# or
|
96
108
|
|
97
|
-
|
98
|
-
|
99
|
-
|
109
|
+
def slug(title)
|
110
|
+
title && title.strip.downcase.tr_s('^[a-z0-9]', '-')
|
111
|
+
end
|
100
112
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
113
|
+
# do it with a default
|
114
|
+
def slug(title)
|
115
|
+
Maybe(title).strip.downcase.tr_s('^[a-z0-9]', '-')._('unknown-title')
|
116
|
+
end
|
117
|
+
```
|
105
118
|
|
106
119
|
### Object#_?
|
107
120
|
Works similar to the Elvis operator _? - ruby does not allow ?: as operator and use it like the excellent [andand](https://github.com/raganwald/andand)
|
108
121
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
122
|
+
```ruby
|
123
|
+
require 'monadic/core_ext/object' # this will import _? into the global Object
|
124
|
+
nil._? == Nothing
|
125
|
+
"foo"._? == 'foo'
|
126
|
+
{}._?.a.b == Nothing
|
127
|
+
{}._?[:foo] == Nothing
|
128
|
+
```
|
114
129
|
|
115
130
|
In fact this is a shortcut notation for `Maybe(obj)`
|
116
131
|
|
@@ -124,61 +139,70 @@ What is specific to this implementation is that exceptions are caught within the
|
|
124
139
|
|
125
140
|
The `Either()` wrapper will treat all falsey values `nil`, `false` or `empty?` as a `Failure` and all others as `Success`. If that does not suit you, use `Success` or `Failure` only.
|
126
141
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
142
|
+
```ruby
|
143
|
+
result = parse_and_validate_params(params). # must return a Success or Failure inside
|
144
|
+
bind ->(user_id) { User.find(user_id) }. # if #find returns null it will become a Failure
|
145
|
+
bind ->(user) { authorized?(user); user }. # if authorized? raises an Exception, it will be a Failure
|
146
|
+
bind ->(user) { UserDecorator(user) }
|
147
|
+
|
148
|
+
if result.success?
|
149
|
+
@user = result.fetch # result.fetch or result._ contains the
|
150
|
+
render 'page'
|
151
|
+
else
|
152
|
+
@error = result.fetch
|
153
|
+
render 'error_page'
|
154
|
+
end
|
155
|
+
```
|
139
156
|
|
140
157
|
You can use alternate syntaxes to achieve the same goal:
|
141
158
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
159
|
+
```ruby
|
160
|
+
# block and Haskell like >= operator
|
161
|
+
Either(operation).
|
162
|
+
>= { successful_method }.
|
163
|
+
>= { failful_operation }
|
146
164
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
165
|
+
# start with a Success, for instance a parameter
|
166
|
+
Success('pzol').
|
167
|
+
bind ->(previous) { good }.
|
168
|
+
bind -> { bad }
|
151
169
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
170
|
+
Either.chain do
|
171
|
+
bind -> { good } # >= is not supported for Either.chain, only bind
|
172
|
+
bind -> { better } # better returns Success(some_int)
|
173
|
+
bind ->(previous_result) { previous_result + 1 }
|
174
|
+
end
|
157
175
|
|
158
|
-
|
159
|
-
|
176
|
+
either = Either(something)
|
177
|
+
either += truth? Success('truth, only the truth') : Failure('lies, damn lies')
|
178
|
+
```
|
160
179
|
|
161
180
|
Exceptions are wrapped into a Failure:
|
162
181
|
|
163
|
-
|
164
|
-
|
182
|
+
```ruby
|
183
|
+
Either(true).
|
184
|
+
bind -> { fail 'get me out of here' } # return a Failure(RuntimeError)
|
185
|
+
```
|
165
186
|
|
166
187
|
Another example:
|
167
188
|
|
168
|
-
|
169
|
-
|
170
|
-
|
189
|
+
```ruby
|
190
|
+
Success(params).
|
191
|
+
bind ->(params) { Either(params.fetch(:path)) } # fails if params does not contain :path
|
192
|
+
bind ->(path) { load_stuff(params) } #
|
193
|
+
```
|
171
194
|
|
172
195
|
Storing intermediate results in instance variables is possible, although it is not very elegant:
|
173
196
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
result == Success(101)
|
197
|
+
```ruby
|
198
|
+
result = Either.chain do
|
199
|
+
bind { @map = { one: 1, two: 2 } }
|
200
|
+
bind { @map.fetch(:one) }
|
201
|
+
bind { |p| Success(p + 100) }
|
202
|
+
end
|
181
203
|
|
204
|
+
result == Success(101)
|
205
|
+
```
|
182
206
|
|
183
207
|
### Validation
|
184
208
|
The Validation applicative functor, takes a list of checks within a block. Each check must return either Success of Failure.
|
@@ -187,60 +211,68 @@ Within the Failure() provide the reason why the check failed.
|
|
187
211
|
|
188
212
|
Example:
|
189
213
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
}
|
199
|
-
|
200
|
-
check_sobriety = ->(sobriety) {
|
201
|
-
case sobriety
|
202
|
-
when :sober, :tipsy; Success(sobriety)
|
203
|
-
when :drunk ; Failure('No drunks allowed')
|
204
|
-
else Failure("Sobriety state '#{sobriety}' is not allowed")
|
205
|
-
end
|
206
|
-
}
|
207
|
-
|
208
|
-
check_gender = ->(gender) {
|
209
|
-
gender == :male || gender == :female ? Success(gender) : Failure("Invalid gender #{gender}")
|
210
|
-
}
|
211
|
-
|
212
|
-
Validation() do
|
213
|
-
check { check_age.(person.age); }
|
214
|
-
check { check_sobriety.(person.sobriety) }
|
215
|
-
check { check_gender.(person.gender) }
|
216
|
-
end
|
214
|
+
```ruby
|
215
|
+
def validate(person)
|
216
|
+
check_age = ->(age_expr) {
|
217
|
+
age = age_expr.to_i
|
218
|
+
case
|
219
|
+
when age <= 0; Failure('Age must be > 0')
|
220
|
+
when age > 130; Failure('Age must be < 130')
|
221
|
+
else Success(age)
|
217
222
|
end
|
223
|
+
}
|
224
|
+
|
225
|
+
check_sobriety = ->(sobriety) {
|
226
|
+
case sobriety
|
227
|
+
when :sober, :tipsy; Success(sobriety)
|
228
|
+
when :drunk ; Failure('No drunks allowed')
|
229
|
+
else Failure("Sobriety state '#{sobriety}' is not allowed")
|
230
|
+
end
|
231
|
+
}
|
232
|
+
|
233
|
+
check_gender = ->(gender) {
|
234
|
+
gender == :male || gender == :female ? Success(gender) : Failure("Invalid gender #{gender}")
|
235
|
+
}
|
236
|
+
|
237
|
+
Validation() do
|
238
|
+
check { check_age.(person.age); }
|
239
|
+
check { check_sobriety.(person.sobriety) }
|
240
|
+
check { check_gender.(person.gender) }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
```
|
218
244
|
|
219
245
|
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.
|
220
246
|
|
221
|
-
See also
|
247
|
+
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)
|
222
248
|
|
223
249
|
### Monad
|
224
250
|
All Monads inherit from this class. Standalone it is an Identity monad. Not useful on its own. It's methods are usable on all its descendants.
|
225
251
|
|
226
252
|
__#map__ is used to map the inner value
|
227
253
|
|
228
|
-
|
229
|
-
|
254
|
+
```ruby
|
255
|
+
Monad.unit('FOO').map(&:capitalize).map {|v| "Hello #{v}"} == Monad(Hello Foo)
|
256
|
+
Monad.unit([1,2]).map {|v| v + 1} == Monad([2, 3])
|
257
|
+
```
|
230
258
|
|
231
259
|
__#bind__ allows (priviledged) access to the boxed value. This is the traditional _no-magic_ `#bind` as found in Haskell,
|
232
|
-
You are responsible for re-wrapping the value into a Monad again.
|
260
|
+
You` are responsible for re-wrapping the value into a Monad again.
|
233
261
|
|
234
|
-
|
235
|
-
|
236
|
-
|
262
|
+
```ruby
|
263
|
+
# due to the way it works, it will simply return the value, don't rely on this though, different Monads may
|
264
|
+
# implement bind differently (e.g. Maybe involves some _magic_)
|
265
|
+
Monad.unit('foo').bind(&:capitalize) == Foo
|
237
266
|
|
238
|
-
|
239
|
-
|
267
|
+
# proper use
|
268
|
+
Monad.unit('foo').bind {|v| Monad.unit(v.capitalize) } == Monad(Foo)
|
269
|
+
```
|
240
270
|
|
241
271
|
__#fetch__ extracts the inner value of the Monad, some Monads will override this standard behaviour, e.g. the Maybe Monad
|
242
272
|
|
243
|
-
|
273
|
+
```ruby
|
274
|
+
Monad.unit('foo').fetch == "foo"
|
275
|
+
```
|
244
276
|
|
245
277
|
## References
|
246
278
|
|
data/examples/validation.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'monadic'
|
2
2
|
|
3
|
+
class AgeTooLowError < ArgumentError; end
|
4
|
+
class AgeTooHighError < ArgumentError; end
|
5
|
+
|
3
6
|
valid_age = ->(age_expr) {
|
4
7
|
age = age_expr.to_i
|
5
8
|
case
|
6
|
-
when age <= 0; Failure(
|
7
|
-
when age > 130; Failure(
|
9
|
+
when age <= 0; Failure(AgeTooLowError.new(age_expr))
|
10
|
+
when age > 130; Failure(AgeTooHighError.new(age_expr))
|
8
11
|
else Success(age)
|
9
12
|
end
|
10
13
|
}
|
@@ -21,11 +24,14 @@ params = {age: 32, name: 'Andrzej', postcode: '50000'}
|
|
21
24
|
Person = Struct.new(:name, :age)
|
22
25
|
|
23
26
|
result = Validation() do
|
24
|
-
check { valid_age.(params[:age]) }
|
25
27
|
check { valid_name.(params[:name]) }
|
28
|
+
check { valid_age.(params[:age]) }
|
26
29
|
end
|
27
30
|
|
28
31
|
case result
|
29
32
|
when Failure; puts "Something went wrong: #{result.fetch}"
|
30
33
|
when Success; person = Person.new(*result.fetch); puts "We have a person #{person.inspect}"
|
31
34
|
end
|
35
|
+
|
36
|
+
# Failure: Something went wrong: [#<AgeTooHighError: 200>]
|
37
|
+
# Success: We have a person #<struct Person name="Andrzej", age=32>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'monadic'
|
2
|
+
|
3
|
+
Person = Struct.new(:name, :age)
|
4
|
+
|
5
|
+
module PersonValidation
|
6
|
+
class AgeTooLowError < ArgumentError; end
|
7
|
+
class AgeTooHighError < ArgumentError; end
|
8
|
+
|
9
|
+
def self.validate(params)
|
10
|
+
Validation() do
|
11
|
+
extend PersonValidation
|
12
|
+
check { valid_age(params[:age]) }
|
13
|
+
check { valid_name(params[:name])}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid_age(expr)
|
18
|
+
age = expr.to_i
|
19
|
+
case
|
20
|
+
when expr.nil?; Failure(ArgumentError.new(:age))
|
21
|
+
when age <= 0; Failure(AgeTooLowError.new(expr))
|
22
|
+
when age > 130; Failure(AgeTooHighError.new(expr))
|
23
|
+
else Success(age)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_name(name)
|
28
|
+
case
|
29
|
+
when name =~ /\w{3,99}/i; Success(name)
|
30
|
+
else Failure('Invalid name')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module PersonBuild
|
36
|
+
def self.build(params)
|
37
|
+
result = PersonValidation.validate(params)
|
38
|
+
case result
|
39
|
+
when Failure; result
|
40
|
+
when Success; Person.new(*result.fetch)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
p person1 = PersonBuild.build({ name: 'Andrzej' }) # Failure([#<ArgumentError: age>])
|
46
|
+
p person2 = PersonBuild.build({ name: 'Andrzej', age: 32 }) # <struct Person name=32, age="Andrzej">
|
data/lib/monadic/either.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
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
|
+
include Monadic::Monad
|
4
5
|
def self.chain(initial=nil, &block)
|
5
6
|
Either::Chain.new(&block).call(initial)
|
6
7
|
end
|
@@ -24,8 +25,8 @@ module Monadic
|
|
24
25
|
|
25
26
|
begin
|
26
27
|
Either(call(proc, block))
|
27
|
-
rescue
|
28
|
-
Failure(
|
28
|
+
rescue StandardError => error
|
29
|
+
Failure(error)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
alias :>= :bind
|
data/lib/monadic/maybe.rb
CHANGED
data/lib/monadic/monad.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
module Monadic
|
2
|
-
|
3
|
-
def self.unit(value)
|
4
|
-
new(value)
|
5
|
-
end
|
6
|
-
|
2
|
+
module Monad
|
7
3
|
def initialize(value)
|
8
4
|
@value = join(value)
|
9
5
|
end
|
@@ -41,7 +37,7 @@ module Monadic
|
|
41
37
|
Array(@value)
|
42
38
|
end
|
43
39
|
alias :to_a :to_ary
|
44
|
-
|
40
|
+
|
45
41
|
# Return the string representation of the Monad
|
46
42
|
def to_s
|
47
43
|
pretty_class_name = self.class.name.split('::')[-1]
|
data/lib/monadic/version.rb
CHANGED
data/spec/either_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe Monadic::Either do
|
|
43
43
|
Success(nil).bind { nil }.should == Failure(nil)
|
44
44
|
end
|
45
45
|
|
46
|
-
it 'catches exceptions and returns them as Failure' do
|
46
|
+
it 'catches StandardError exceptions and returns them as Failure' do
|
47
47
|
either = Success(nil).
|
48
48
|
bind { raise 'error' }
|
49
49
|
|
data/spec/monad_spec.rb
CHANGED
@@ -1,34 +1,41 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Monadic::Monad do
|
4
|
+
class Identity
|
5
|
+
include Monadic::Monad
|
6
|
+
def self.unit(value)
|
7
|
+
new(value)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
4
11
|
it_behaves_like 'a Monad' do
|
5
|
-
let(:monad) {
|
12
|
+
let(:monad) { Identity }
|
6
13
|
end
|
7
14
|
|
8
15
|
it '#to_s shows the monad name and its value' do
|
9
|
-
|
10
|
-
|
11
|
-
|
16
|
+
Identity.unit(1).to_s.should == 'Identity(1)'
|
17
|
+
Identity.unit(nil).to_s.should == 'Identity(nil)'
|
18
|
+
Identity.unit([1, 2]).map(&:to_s).should == Identity.unit(["1", "2"])
|
12
19
|
|
13
20
|
# can be done also
|
14
|
-
|
21
|
+
Identity.unit([1, 2]).bind {|v| Identity.unit(v.map(&:to_s)) }.should == Identity.unit(["1", "2"])
|
15
22
|
end
|
16
23
|
|
17
24
|
describe '#map' do
|
18
25
|
it 'applies the function to the underlying value directly' do
|
19
|
-
|
20
|
-
|
26
|
+
Identity.unit(1).map {|v| v + 2}.should == Identity.unit(3)
|
27
|
+
Identity.unit('foo').map(&:upcase).should == Identity.unit('FOO')
|
21
28
|
end
|
22
29
|
|
23
30
|
it 'delegates #map to an underlying collection and wraps the resulting collection' do
|
24
|
-
|
25
|
-
|
31
|
+
Identity.unit([1,2]).map {|v| v + 1}.should == Identity.unit([2, 3])
|
32
|
+
Identity.unit(['foo', 'bar']).map(&:upcase).should == Identity.unit(['FOO', 'BAR'])
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
29
36
|
describe '#to_ary #to_a' do
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
Identity.unit([1, 2]).to_a.should == [1, 2]
|
38
|
+
Identity.unit(nil).to_a.should == []
|
39
|
+
Identity.unit('foo').to_a.should == ['foo']
|
33
40
|
end
|
34
41
|
end
|
data/spec/spec_helper.rb
CHANGED
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.1.
|
4
|
+
version: 0.1.1
|
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-05-
|
12
|
+
date: 2012-05-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
142
|
- examples/validation.rb
|
143
|
+
- examples/validation_module.rb
|
143
144
|
- lib/monadic.rb
|
144
145
|
- lib/monadic/core_ext/object.rb
|
145
146
|
- lib/monadic/either.rb
|