monadic 0.1.1 → 0.2.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.0 (not released)
4
+ **BREAKING CHANGES**
5
+
6
+ `Monad#map` does not recognize `Enumerables` automatically anymore. It just maps, as a `Functor`.
7
+ Instead the new `Monad#flat_map` function operates on the underlying value as on `Enumerable`.
8
+
9
+ `Either#else` allows to exchange the inner value of `Nothing` to an alternative value.
10
+
11
+ Either(false == true).else('false was not true') == Failure(false was not true)
12
+ Success('truth needs no sugar coating').else('all lies') == Success('truth needs no sugar coating')
13
+
3
14
  ## v0.1.1
4
15
 
5
16
  `Either()` coerces only `StandardError` to `Failure`, other exceptions higher in the hierarchy are will break the flow.
data/README.md CHANGED
@@ -64,12 +64,17 @@ Map, select:
64
64
  ```ruby
65
65
  Maybe(123).map { |value| User.find(value) } == Just(someUser) # if user found
66
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
67
+ Maybe([1,2]).map { |value| value.to_s } == Just(["1, 2"]) # for all Enumerables
68
68
 
69
69
  Maybe('foo').select { |value| value.start_with?('f') } == Just('foo')
70
70
  Maybe('bar').select { |value| value.start_with?('f') } == Nothing
71
71
  ```
72
72
 
73
+ For `Enumerable` use `#flat_map`:
74
+ ```ruby
75
+ Maybe([1, 2]).flat_map {|v| v + 1 } == Just([2, 3])
76
+ ```
77
+
73
78
  Treat it like an array:
74
79
 
75
80
  ```ruby
@@ -137,7 +142,7 @@ What is specific to this implementation is that exceptions are caught within the
137
142
  `Success` represents a successfull execution of an operation (Right in Scala, Haskell).
138
143
  `Failure` represents a failure to execute an operation (Left in Scala, Haskell).
139
144
 
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.
145
+ The `Either()` wrapper works like a coercon. It 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. However as ruby cannot enforce the value returned from within a bind, it will auto-magically coerce the return value into an `Either`.
141
146
 
142
147
  ```ruby
143
148
  result = parse_and_validate_params(params). # must return a Success or Failure inside
@@ -192,6 +197,13 @@ Success(params).
192
197
  bind ->(path) { load_stuff(params) } #
193
198
  ```
194
199
 
200
+ `Either#else` allows to provide alternate values in case of `Failure`:
201
+
202
+ ```ruby
203
+ Either(false == true).else('false was not true') == Failure(false was not true)
204
+ Success('truth needs no sugar coating').else('all lies') == Success('truth needs no sugar coating')
205
+ ```
206
+
195
207
  Storing intermediate results in instance variables is possible, although it is not very elegant:
196
208
 
197
209
  ```ruby
@@ -247,13 +259,21 @@ The above example, returns either `Success([32, :sober, :male])` or `Failure(['A
247
259
  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)
248
260
 
249
261
  ### Monad
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.
262
+ All Monads include this module. Standalone it is an Identity monad. Not useful on its own. It's methods are usable on all its descendants.
251
263
 
252
264
  __#map__ is used to map the inner value
253
265
 
254
266
  ```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])
267
+ # minimum implementation of a monad
268
+ class Identity
269
+ include Monadic::Monad
270
+ def self.unit(value)
271
+ new(value)
272
+ end
273
+ end
274
+
275
+ Identity.unit('FOO').map(&:capitalize).map {|v| "Hello #{v}"} == Identity(Hello Foo)
276
+ Identity.unit([1,2]).map {|v| v + 1} == Identity([2, 3])
257
277
  ```
258
278
 
259
279
  __#bind__ allows (priviledged) access to the boxed value. This is the traditional _no-magic_ `#bind` as found in Haskell,
@@ -262,16 +282,16 @@ You` are responsible for re-wrapping the value into a Monad again.
262
282
  ```ruby
263
283
  # due to the way it works, it will simply return the value, don't rely on this though, different Monads may
264
284
  # implement bind differently (e.g. Maybe involves some _magic_)
265
- Monad.unit('foo').bind(&:capitalize) == Foo
285
+ Identity.unit('foo').bind(&:capitalize) == Foo
266
286
 
267
287
  # proper use
268
- Monad.unit('foo').bind {|v| Monad.unit(v.capitalize) } == Monad(Foo)
288
+ Identity.unit('foo').bind {|v| Identity.unit(v.capitalize) } == Identity(Foo)
269
289
  ```
270
290
 
271
291
  __#fetch__ extracts the inner value of the Monad, some Monads will override this standard behaviour, e.g. the Maybe Monad
272
292
 
273
293
  ```ruby
274
- Monad.unit('foo').fetch == "foo"
294
+ Identity.unit('foo').fetch == "foo"
275
295
  ```
276
296
 
277
297
  ## References
@@ -32,6 +32,13 @@ module Monadic
32
32
  alias :>= :bind
33
33
  alias :+ :bind
34
34
 
35
+ # If it is a Failure it will return a new Failure with the provided value
36
+ # @return [Success, Failure]
37
+ def else(value)
38
+ return Failure(value) if failure?
39
+ return self
40
+ end
41
+
35
42
  def fetch(default=@value)
36
43
  return default if failure?
37
44
  return @value
data/lib/monadic/maybe.rb CHANGED
@@ -18,8 +18,9 @@ module Monadic
18
18
 
19
19
  # @return [Failure, Success] the Maybe Monad filtered with the block or proc expression
20
20
  def select(proc = nil, &block)
21
- return Maybe(@value.select(&block)) if @value.is_a?(::Enumerable)
22
- return Nothing unless (proc || block).call(@value)
21
+ func = (proc || block)
22
+ return Maybe(@value.select {|v| func.call(v) }) if @value.respond_to? :select
23
+ return Nothing unless func.call(@value)
23
24
  return self
24
25
  end
25
26
 
data/lib/monadic/monad.rb CHANGED
@@ -23,15 +23,19 @@ module Monadic
23
23
  end
24
24
  alias :_ :fetch
25
25
 
26
- # A functor applying the proc or block on the boxed `value` and returning the Monad with the transformed values.
27
- # If the underlying `value` is an `Enumerable`, the map is applied on each element of the collection.
26
+ # A Functor applying the proc or block on the boxed `value` and returning the Monad with the transformed values.
28
27
  # (A -> B) -> M[A] -> M[B]
29
28
  def map(proc = nil, &block)
30
29
  func = (proc || block)
31
- return self.class.unit(@value.map {|v| func.call(v) }) if @value.is_a?(::Enumerable)
32
30
  return self.class.unit(func.call(@value))
33
31
  end
34
32
 
33
+ def flat_map(proc = nil, &block)
34
+ fail "Underlying value does not respond to #map" unless @value.respond_to? :map
35
+ func = (proc || block)
36
+ return self.class.unit(@value.map {|v| func.call(v) }) if @value.respond_to? :map
37
+ end
38
+
35
39
  # @return [Array] a with the values inside the monad
36
40
  def to_ary
37
41
  Array(@value)
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/spec/either_spec.rb CHANGED
@@ -53,6 +53,14 @@ describe Monadic::Either do
53
53
  error.message.should == 'error'
54
54
  end
55
55
 
56
+ it '#else returns an alternative value considered Success if it is Nothing' do
57
+ Failure(false).else(true).should == Failure(true)
58
+ Either(nil).else(true).should == Failure(true)
59
+ Success(true).else(false).should == Success(true)
60
+ Either(true).else(false).should == Success(true)
61
+ Success(false).else(true).should == Success(false)
62
+ end
63
+
56
64
  class User
57
65
  attr :age, :gender, :sobriety
58
66
  def self.find(id)
data/spec/maybe_spec.rb CHANGED
@@ -105,7 +105,7 @@ describe Monadic::Maybe do
105
105
  it 'allows to use map' do
106
106
  Maybe(nil).map { |e| Hash.new(:key => e) }.should == Nothing
107
107
  Maybe('foo').map { |e| Hash.new(:key => e) }.should == Just(Hash.new(:key => 'foo'))
108
- Maybe([1,2]).map { |e| e.to_s }.should == Just(["1", "2"])
108
+ Maybe([1,2]).map { |e| e.to_s }.should == Just("[1, 2]")
109
109
  end
110
110
 
111
111
  it 'allows to use select' do
data/spec/monad_axioms.rb CHANGED
@@ -34,24 +34,5 @@ shared_examples 'a Monad' do
34
34
  id1.should == id2
35
35
  end
36
36
  end
37
-
38
- describe '#map functor' do
39
- add100 = ->(value) { value + 100 }
40
- it 'on value types, returns the transformed value, wrapped in the Monad' do
41
- res = monad.unit(1).map {|v| add100.(v) }
42
- res.should == monad.unit(101)
43
-
44
- res = monad.unit(1).map {|v| monad.unit(v + 100) }
45
- res.should == monad.unit(101)
46
- end
47
-
48
- it 'on enumerables, returns the transformed collection, wrapped in the Monad' do
49
- res = monad.unit([1,2,3]).map {|v| add100.(v) }
50
- res.should == monad.unit([101,102,103])
51
-
52
- # The following does not work... not sure whether it should
53
- # res = monad.unit([1,2,3]).map {|v| monad.unit(v + 100) }
54
- # res.should == monad.unit([101,102,103])
55
- end
56
- end
57
37
  end
38
+
data/spec/monad_spec.rb CHANGED
@@ -15,27 +15,27 @@ describe Monadic::Monad do
15
15
  it '#to_s shows the monad name and its value' do
16
16
  Identity.unit(1).to_s.should == 'Identity(1)'
17
17
  Identity.unit(nil).to_s.should == 'Identity(nil)'
18
- Identity.unit([1, 2]).map(&:to_s).should == Identity.unit(["1", "2"])
18
+ Identity.unit([1, 2]).map(&:to_s).should == Identity.unit("[1, 2]")
19
19
 
20
20
  # can be done also
21
21
  Identity.unit([1, 2]).bind {|v| Identity.unit(v.map(&:to_s)) }.should == Identity.unit(["1", "2"])
22
22
  end
23
23
 
24
- describe '#map' do
25
- it 'applies the function to the underlying value directly' do
26
- Identity.unit(1).map {|v| v + 2}.should == Identity.unit(3)
27
- Identity.unit('foo').map(&:upcase).should == Identity.unit('FOO')
28
- end
24
+ it '#map applies the function to the underlying value directly' do
25
+ Identity.unit(1).map {|v| v + 2}.should == Identity.unit(3)
26
+ Identity.unit('foo').map(&:upcase).should == Identity.unit('FOO')
27
+ end
29
28
 
30
- it 'delegates #map to an underlying collection and wraps the resulting collection' do
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'])
33
- end
29
+ it 'delegates #flat_map to an underlying collection and wraps the resulting collection' do
30
+ Identity.unit([1,2]).flat_map {|v| v + 1}.should == Identity.unit([2, 3])
31
+ Identity.unit(['foo', 'bar']).flat_map(&:upcase).should == Identity.unit(['FOO', 'BAR'])
32
+ expect { Identity.unit(1).flat_map {|v| v + 1 } }.to raise_error(RuntimeError)
34
33
  end
35
34
 
36
- describe '#to_ary #to_a' do
35
+ it '#to_ary #to_a' do
37
36
  Identity.unit([1, 2]).to_a.should == [1, 2]
38
37
  Identity.unit(nil).to_a.should == []
39
38
  Identity.unit('foo').to_a.should == ['foo']
40
39
  end
40
+
41
41
  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.1.1
4
+ version: 0.2.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-08 00:00:00.000000000 Z
12
+ date: 2012-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec