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 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