monadic 0.1.0 → 0.1.1
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 +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
|