monadic 0.0.7 → 0.1.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 +14 -0
- data/README.md +3 -1
- data/examples/validation.rb +31 -0
- data/lib/monadic/either.rb +33 -23
- data/lib/monadic/maybe.rb +4 -6
- data/lib/monadic/monad.rb +7 -0
- data/lib/monadic/validation.rb +9 -4
- data/lib/monadic/version.rb +1 -1
- data/spec/either_spec.rb +16 -1
- data/spec/maybe_spec.rb +1 -1
- data/spec/monad_spec.rb +10 -0
- data/spec/validation_spec.rb +1 -1
- metadata +3 -2
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.1.0
|
4
|
+
|
5
|
+
You can now chain Eithers with `+` without providing a block or a proc:
|
6
|
+
|
7
|
+
Success(1) + Failure(2) == Failure(2)
|
8
|
+
Failure(1) + Failure(2) == Failure(1)
|
9
|
+
|
10
|
+
All monads now have the `#to_ary` and `#to_a` method, which coerces the inner value into an `Array`.
|
11
|
+
|
12
|
+
I am considering the Api now almost stable, the only thing I am not so sure about,
|
13
|
+
is whether to apply the magic coercion or not.
|
14
|
+
|
15
|
+
`Validation()` now returns the successfully validated values. See examples/validation.rb
|
16
|
+
|
3
17
|
## v0.0.7
|
4
18
|
|
5
19
|
Implements the #map method for all Monads. It works on value types and on Enumerable collections.
|
data/README.md
CHANGED
@@ -216,7 +216,9 @@ Example:
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
-
The above example, returns either `Success()` or `Failure(['Age must be > 0', 'No drunks allowed']) with a list of what went wrong during the validation.
|
219
|
+
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
|
+
|
221
|
+
See also exmaples/validation.rb
|
220
222
|
|
221
223
|
### Monad
|
222
224
|
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.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'monadic'
|
2
|
+
|
3
|
+
valid_age = ->(age_expr) {
|
4
|
+
age = age_expr.to_i
|
5
|
+
case
|
6
|
+
when age <= 0; Failure('Age must be > 0')
|
7
|
+
when age > 130; Failure('Age must be < 130')
|
8
|
+
else Success(age)
|
9
|
+
end
|
10
|
+
}
|
11
|
+
|
12
|
+
valid_name = ->(name) {
|
13
|
+
case
|
14
|
+
when name =~ /\w{3,99}/i; Success(name)
|
15
|
+
else Failure('Invalid name')
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
params = {age: 32, name: 'Andrzej', postcode: '50000'}
|
20
|
+
|
21
|
+
Person = Struct.new(:name, :age)
|
22
|
+
|
23
|
+
result = Validation() do
|
24
|
+
check { valid_age.(params[:age]) }
|
25
|
+
check { valid_name.(params[:name]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
case result
|
29
|
+
when Failure; puts "Something went wrong: #{result.fetch}"
|
30
|
+
when Success; person = Person.new(*result.fetch); puts "We have a person #{person.inspect}"
|
31
|
+
end
|
data/lib/monadic/either.rb
CHANGED
@@ -6,6 +6,7 @@ module Monadic
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.unit(value)
|
9
|
+
return value if value.is_a? Either
|
9
10
|
return Failure.new(value) if value.nil? || (value.respond_to?(:empty?) && value.empty?) || !value
|
10
11
|
return Success.new(value)
|
11
12
|
end
|
@@ -14,37 +15,46 @@ module Monadic
|
|
14
15
|
# are required (Either is abstract).
|
15
16
|
private_class_method :new
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# Allows privileged access to the +Either+'s inner value from within a block.
|
19
|
+
# This block should return a +Success+ or +Failure+ itself. It will be coerced into #Either
|
20
|
+
# @return [Success, Failure]
|
21
|
+
def bind(proc=nil, &block)
|
22
|
+
return self if failure?
|
23
|
+
return concat(proc) if proc.is_a? Either
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
begin
|
26
|
+
Either(call(proc, block))
|
27
|
+
rescue Exception => ex
|
28
|
+
Failure(ex)
|
29
|
+
end
|
23
30
|
end
|
31
|
+
alias :>= :bind
|
32
|
+
alias :+ :bind
|
24
33
|
|
25
34
|
def fetch(default=@value)
|
26
35
|
return default if failure?
|
27
36
|
return @value
|
28
37
|
end
|
38
|
+
|
29
39
|
alias :_ :fetch
|
40
|
+
def success?
|
41
|
+
is_a? Success
|
42
|
+
end
|
30
43
|
|
31
|
-
def
|
32
|
-
|
44
|
+
def failure?
|
45
|
+
is_a? Failure
|
46
|
+
end
|
33
47
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
Failure(ex)
|
44
|
-
end
|
48
|
+
private
|
49
|
+
def call(proc=nil, block)
|
50
|
+
func = (proc || block)
|
51
|
+
raise "No block or lambda given" unless func.is_a? Proc
|
52
|
+
(func.arity == 0 ? func.call : func.call(@value)) || Failure(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
def concat(other)
|
56
|
+
failure? ? self : other
|
45
57
|
end
|
46
|
-
alias :>= :bind
|
47
|
-
alias :+ :bind
|
48
58
|
end
|
49
59
|
|
50
60
|
class Either::Chain
|
@@ -64,7 +74,7 @@ module Monadic
|
|
64
74
|
end
|
65
75
|
end
|
66
76
|
|
67
|
-
# @private instance and class methods for Success and Failure
|
77
|
+
# @private instance and class methods for +Success+ and +Failure+
|
68
78
|
module SuccessFailure
|
69
79
|
def self.included(base)
|
70
80
|
base.class_eval do
|
@@ -103,8 +113,8 @@ module Monadic
|
|
103
113
|
Success.new(value)
|
104
114
|
end
|
105
115
|
|
106
|
-
#
|
107
|
-
# @return [Success, Failure] depending whether +value+ is falsey
|
116
|
+
# Coerces any value into either a +Success+ or a +Failure+.
|
117
|
+
# @return [Success, Failure] depending whether +value+ is falsey i.e. nil, false, Enumerable#empty? become +Failure+, all other `Success`
|
108
118
|
def Either(value)
|
109
119
|
Either.unit(value)
|
110
120
|
end
|
data/lib/monadic/maybe.rb
CHANGED
@@ -9,16 +9,12 @@ module Monadic
|
|
9
9
|
# are required (Maybe is abstract).
|
10
10
|
private_class_method :new
|
11
11
|
|
12
|
+
# @return true if the underlying object responds to #empty?, false otherwise
|
12
13
|
def empty?
|
13
14
|
@value.respond_to?(:empty?) && @value.empty?
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
return [@value].flatten if @value.respond_to? :flatten
|
18
|
-
return [@value]
|
19
|
-
end
|
20
|
-
alias :to_a :to_ary
|
21
|
-
|
17
|
+
# @return [Failure, Success] the Maybe Monad filtered with the block or proc expression
|
22
18
|
def select(proc = nil, &block)
|
23
19
|
return Maybe(@value.select(&block)) if @value.is_a?(::Enumerable)
|
24
20
|
return Nothing unless (proc || block).call(@value)
|
@@ -30,6 +26,7 @@ module Monadic
|
|
30
26
|
end
|
31
27
|
end
|
32
28
|
|
29
|
+
# Represents an existing value
|
33
30
|
class Just < Maybe
|
34
31
|
public_class_method :new
|
35
32
|
|
@@ -43,6 +40,7 @@ module Monadic
|
|
43
40
|
end
|
44
41
|
end
|
45
42
|
|
43
|
+
# Represents a NullObject
|
46
44
|
class Nothing < Maybe
|
47
45
|
class << self
|
48
46
|
def fetch(default=nil)
|
data/lib/monadic/monad.rb
CHANGED
@@ -8,6 +8,7 @@ module Monadic
|
|
8
8
|
@value = join(value)
|
9
9
|
end
|
10
10
|
|
11
|
+
# Allows priviledged access to the inner value of the Monad from within the block
|
11
12
|
def bind(proc=nil, &block)
|
12
13
|
(proc || block).call(@value)
|
13
14
|
end
|
@@ -35,6 +36,12 @@ module Monadic
|
|
35
36
|
return self.class.unit(func.call(@value))
|
36
37
|
end
|
37
38
|
|
39
|
+
# @return [Array] a with the values inside the monad
|
40
|
+
def to_ary
|
41
|
+
Array(@value)
|
42
|
+
end
|
43
|
+
alias :to_a :to_ary
|
44
|
+
|
38
45
|
# Return the string representation of the Monad
|
39
46
|
def to_s
|
40
47
|
pretty_class_name = self.class.name.split('::')[-1]
|
data/lib/monadic/validation.rb
CHANGED
@@ -9,20 +9,25 @@ module Monadic
|
|
9
9
|
# Validation is not a monad, but an deemed an applicative functor
|
10
10
|
class Validation
|
11
11
|
def initialize
|
12
|
-
@result =
|
12
|
+
@result = []
|
13
13
|
end
|
14
14
|
|
15
15
|
def call(&block)
|
16
16
|
instance_eval(&block)
|
17
|
+
|
18
|
+
if @result.any?(&:failure?)
|
19
|
+
Failure(@result.select(&:failure?).map(&:fetch))
|
20
|
+
else
|
21
|
+
Success(@result.select(&:success?).map(&:fetch))
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
#
|
20
26
|
def check(proc=nil, &block)
|
21
27
|
result = (proc || block).call
|
22
28
|
raise NotEitherError, "Expected #{result.inspect} to be an Either" unless result.is_a? Either
|
23
|
-
|
24
|
-
|
25
|
-
@result
|
29
|
+
# @failure.fetch << result.fetch) if result.failure?
|
30
|
+
@result << result
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/monadic/version.rb
CHANGED
data/spec/either_spec.rb
CHANGED
@@ -75,6 +75,12 @@ describe Monadic::Either do
|
|
75
75
|
Failure.should === Either(nil)
|
76
76
|
end
|
77
77
|
|
78
|
+
it 'Either on an either returns the Either self to allow coercion' do
|
79
|
+
Either(Success(1)).should == Success(1)
|
80
|
+
Success(Success(1)).should == Success(1)
|
81
|
+
Failure(Failure(1)).should == Failure(1)
|
82
|
+
end
|
83
|
+
|
78
84
|
it 'Either with a non-falsey value returns success' do
|
79
85
|
Success.should === Either(1)
|
80
86
|
Success.should === Either(true)
|
@@ -166,6 +172,15 @@ describe Monadic::Either do
|
|
166
172
|
either.should == Success(1)
|
167
173
|
end
|
168
174
|
|
175
|
+
it 'allows to add Eithers without specifying a block or proc' do
|
176
|
+
(Failure(1) + Failure(2)).should == Failure(1)
|
177
|
+
(Success(1) + Failure(2)).should == Failure(2)
|
178
|
+
(Success(1) + Success(2)).should == Success(2)
|
179
|
+
(Failure(1) >= Failure(2)).should == Failure(1)
|
180
|
+
(Success(1) >= Failure(2)).should == Failure(2)
|
181
|
+
(Success(1) >= Success(2)).should == Success(2)
|
182
|
+
end
|
183
|
+
|
169
184
|
it 'allows to check whether the result was a success or failure' do
|
170
185
|
Success(0).success?.should be_true
|
171
186
|
Success(1).failure?.should be_false
|
@@ -174,7 +189,7 @@ describe Monadic::Either do
|
|
174
189
|
Failure(3).failure?.should be_true
|
175
190
|
end
|
176
191
|
|
177
|
-
it '
|
192
|
+
it 'supports Either.chain' do
|
178
193
|
Either.chain do
|
179
194
|
bind -> { Success(1) }
|
180
195
|
bind -> { Success(2) }
|
data/spec/maybe_spec.rb
CHANGED
@@ -115,7 +115,7 @@ describe Monadic::Maybe do
|
|
115
115
|
Maybe([1, 2]).select { |e| e == 1 }.should == Just([1])
|
116
116
|
end
|
117
117
|
|
118
|
-
it 'acts as an array' do
|
118
|
+
it 'acts as an array (inherited from Monad)' do
|
119
119
|
Maybe('foo').to_a.should == ['foo']
|
120
120
|
Maybe(['foo', 'bar']).to_a.should == ['foo', 'bar']
|
121
121
|
Maybe(nil).to_a.should == []
|
data/spec/monad_spec.rb
CHANGED
@@ -8,6 +8,10 @@ describe Monadic::Monad do
|
|
8
8
|
it '#to_s shows the monad name and its value' do
|
9
9
|
Monad.unit(1).to_s.should == 'Monad(1)'
|
10
10
|
Monad.unit(nil).to_s.should == 'Monad(nil)'
|
11
|
+
Monad.unit([1, 2]).map(&:to_s).should == Monad.unit(["1", "2"])
|
12
|
+
|
13
|
+
# can be done also
|
14
|
+
Monad.unit([1, 2]).bind {|v| Monad.unit(v.map(&:to_s)) }.should == Monad.unit(["1", "2"])
|
11
15
|
end
|
12
16
|
|
13
17
|
describe '#map' do
|
@@ -21,4 +25,10 @@ describe Monadic::Monad do
|
|
21
25
|
Monad.unit(['foo', 'bar']).map(&:upcase).should == Monad.unit(['FOO', 'BAR'])
|
22
26
|
end
|
23
27
|
end
|
28
|
+
|
29
|
+
describe '#to_ary #to_a' do
|
30
|
+
Monad.unit([1, 2]).to_a.should == [1, 2]
|
31
|
+
Monad.unit(nil).to_a.should == []
|
32
|
+
Monad.unit('foo').to_a.should == ['foo']
|
33
|
+
end
|
24
34
|
end
|
data/spec/validation_spec.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.0
|
4
|
+
version: 0.1.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-05-
|
12
|
+
date: 2012-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- LICENSE
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
|
+
- examples/validation.rb
|
142
143
|
- lib/monadic.rb
|
143
144
|
- lib/monadic/core_ext/object.rb
|
144
145
|
- lib/monadic/either.rb
|