monadic 0.1.1 → 0.2.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,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