monadic 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
- def success?
18
- is_a? Success
19
- end
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
- def failure?
22
- is_a? Failure
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 bind(proc=nil, &block)
32
- return self if failure?
44
+ def failure?
45
+ is_a? Failure
46
+ end
33
47
 
34
- begin
35
- result = if proc && proc.arity == 0
36
- then proc.call
37
- else (proc || block).call(@value)
38
- end
39
- result ||= Failure(nil)
40
- result = Either(result) unless result.is_a? Either
41
- result
42
- rescue Exception => ex
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
- # Magic factory
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
- def to_ary
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]
@@ -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 = Success([])
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
- @result = Failure(@result.fetch << result.fetch) if result.failure?
24
-
25
- @result
29
+ # @failure.fetch << result.fetch) if result.failure?
30
+ @result << result
26
31
  end
27
32
  end
28
33
  end
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.0.7"
2
+ VERSION = "0.1.0"
3
3
  end
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 'supprts Either.chain' do
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
@@ -42,6 +42,6 @@ describe Monadic::Validation do
42
42
 
43
43
  success = validate(Person.new(age = 30, gender = :male, sobriety = :sober, name = 'test'))
44
44
  success.should be_a Success
45
- success.should == Success([])
45
+ success.should == Success([30, :sober, :male])
46
46
  end
47
47
  end
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.7
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-05 00:00:00.000000000 Z
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