monadic 0.0.6 → 0.0.7

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.0.7
4
+
5
+ Implements the #map method for all Monads. It works on value types and on Enumerable collections.
6
+ Provide a proc or a block and it will return a transformed value or collection boxed back in the monad.
7
+
8
+ Monad.unit('FOO').map(&:capitalize).map {|v| "Hello #{v}"} == Monad(Hello Foo)
9
+
10
+ Add the Elvis operator _? - ruby does not allow ?: as operator and use it like the excellent [andand](https://github.com/raganwald/andand)
11
+
12
+ nil._? == Nothing
13
+ "foo"._? == 'foo'
14
+ {}._?.a.b == Nothing
15
+ {}._?[:foo] == Nothing
16
+
3
17
  ## v0.0.6
4
18
  **Contains Breaking Changes**
5
19
 
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Monadic
2
+ [![Build Status](https://secure.travis-ci.org/pzol/monadic.png?branch=master)](http://travis-ci.org/pzol/monadic)
2
3
 
3
4
  helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in [Scala](http://www.scala-lang.org/) and [Haskell](http://www.haskell.org/) to my ruby projects.
4
5
 
@@ -18,9 +19,11 @@ A monad is most effectively described as a computation that eventually returns a
18
19
  ## Usage
19
20
 
20
21
  ### Maybe
21
- Is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Option, stays in the Option'.
22
+ Most people probably will be interested in the Maybe monad, as it solves the problem with nil invocations, similar to [andand](https://github.com/raganwald/andand) and others.
22
23
 
23
- Option(User.find(123)).name._ # ._ is a shortcut for .fetch
24
+ Maybe is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Maybe, stays in the Maybe'.
25
+
26
+ Maybe(User.find(123)).name._ # ._ is a shortcut for .fetch
24
27
 
25
28
  # if you prefer the alias Maybe instead of option
26
29
  Maybe(User.find(123)).name._
@@ -76,7 +79,7 @@ Falsey values (kind-of) examples:
76
79
  user.subscribed?.fetch(false) # same as above
77
80
  user.subscribed?.or(false) # same as above
78
81
 
79
- Remember! an Option is never false (in Ruby terms), if you want to know if it is false, call `#empty?` of `#truly?`
82
+ Remember! a Maybe is never false (in Ruby terms), if you want to know if it is false, call `#empty?` of `#truly?`
80
83
 
81
84
  `#truly?` will return true or false, always.
82
85
 
@@ -100,6 +103,17 @@ Slug example
100
103
  Maybe(title).strip.downcase.tr_s('^[a-z0-9]', '-')._('unknown-title')
101
104
  end
102
105
 
106
+ ### Object#_?
107
+ Works similar to the Elvis operator _? - ruby does not allow ?: as operator and use it like the excellent [andand](https://github.com/raganwald/andand)
108
+
109
+ require 'monadic/core_ext/object' # this will import _? into the global Object
110
+ nil._? == Nothing
111
+ "foo"._? == 'foo'
112
+ {}._?.a.b == Nothing
113
+ {}._?[:foo] == Nothing
114
+
115
+ In fact this is a shortcut notation for `Maybe(obj)`
116
+
103
117
  ### Either
104
118
  Its main purpose here to handle errors gracefully, by chaining multiple calls in a functional way and stop evaluating them as soon as the first fails.
105
119
  Assume you need several calls to construct some object in order to be useful, after each you need to check for success. Also you want to catch exceptions and not let them bubble upwards.
@@ -202,6 +216,29 @@ Example:
202
216
  end
203
217
  end
204
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.
220
+
221
+ ### Monad
222
+ 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.
223
+
224
+ __#map__ is used to map the inner value
225
+
226
+ Monad.unit('FOO').map(&:capitalize).map {|v| "Hello #{v}"} == Monad(Hello Foo)
227
+ Monad.unit([1,2]).map {|v| v + 1} == Monad([2, 3])
228
+
229
+ __#bind__ allows (priviledged) access to the boxed value. This is the traditional _no-magic_ `#bind` as found in Haskell,
230
+ You are responsible for re-wrapping the value into a Monad again.
231
+
232
+ # due to the way it works, it will simply return the value, don't rely on this though, different Monads may
233
+ # implement bind differently (e.g. Maybe involves some _magic_)
234
+ Monad.unit('foo').bind(&:capitalize) == Foo
235
+
236
+ # proper use
237
+ Monad.unit('foo').bind {|v| Monad.unit(v.capitalize) } == Monad(Foo)
238
+
239
+ __#fetch__ extracts the inner value of the Monad, some Monads will override this standard behaviour, e.g. the Maybe Monad
240
+
241
+ Monad.unit('foo').fetch == "foo"
205
242
 
206
243
  ## References
207
244
 
@@ -239,7 +276,6 @@ Or install it yourself as:
239
276
  ## Compatibility
240
277
  Monadic is tested under ruby MRI 1.9.2, 1.9.3, jruby 1.9 mode, rbx 1.9 mode.
241
278
 
242
- See the build status [![Build Status](https://secure.travis-ci.org/pzol/monadic.png?branch=master)](http://travis-ci.org/pzol/monadic)
243
279
 
244
280
  ## Contributing
245
281
 
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def _?
3
+ Maybe(self).fetch(nil)
4
+ end
5
+ end
@@ -12,9 +12,7 @@ module Monadic
12
12
 
13
13
  # Initialize is private, because it always would return an instance of Either, but Success or Failure
14
14
  # are required (Either is abstract).
15
- def initialize(value)
16
- raise NoMethodError, "private method `new' called for #{self.class.name}, use `unit' instead"
17
- end
15
+ private_class_method :new
18
16
 
19
17
  def success?
20
18
  is_a? Success
@@ -68,6 +66,13 @@ module Monadic
68
66
 
69
67
  # @private instance and class methods for Success and Failure
70
68
  module SuccessFailure
69
+ def self.included(base)
70
+ base.class_eval do
71
+ public_class_method :new
72
+ extend ClassMethods
73
+ include InstanceMethods
74
+ end
75
+ end
71
76
  module ClassMethods
72
77
  def unit(value)
73
78
  new(value)
@@ -81,13 +86,11 @@ module Monadic
81
86
  end
82
87
 
83
88
  class Success < Either
84
- extend SuccessFailure::ClassMethods
85
- include SuccessFailure::InstanceMethods
89
+ include SuccessFailure
86
90
  end
87
91
 
88
92
  class Failure < Either
89
- extend SuccessFailure::ClassMethods
90
- include SuccessFailure::InstanceMethods
93
+ include SuccessFailure
91
94
  end
92
95
 
93
96
  def Failure(value)
data/lib/monadic/maybe.rb CHANGED
@@ -1,21 +1,5 @@
1
1
  module Monadic
2
- # @api private helps treating `Maybe` like Either in Scala
3
- module ScalaStuff
4
- def map(proc = nil, &block)
5
- return Maybe(@value.map(&block)) if @value.is_a?(Enumerable)
6
- return Maybe((proc || block).call(@value))
7
- end
8
-
9
- def select(proc = nil, &block)
10
- return Maybe(@value.select(&block)) if @value.is_a?(Enumerable)
11
- return Nothing unless (proc || block).call(@value)
12
- return self
13
- end
14
- end
15
-
16
2
  class Maybe < Monad
17
- include ScalaStuff
18
-
19
3
  def self.unit(value)
20
4
  return Nothing if value.nil? || (value.respond_to?(:empty?) && value.empty?)
21
5
  return Just.new(value)
@@ -23,9 +7,7 @@ module Monadic
23
7
 
24
8
  # Initialize is private, because it always would return an instance of Maybe, but Just or Nothing
25
9
  # are required (Maybe is abstract).
26
- def initialize(*args)
27
- raise NoMethodError, "private method `new' called for #{self.class.name}, use `unit' instead"
28
- end
10
+ private_class_method :new
29
11
 
30
12
  def empty?
31
13
  @value.respond_to?(:empty?) && @value.empty?
@@ -37,15 +19,19 @@ module Monadic
37
19
  end
38
20
  alias :to_a :to_ary
39
21
 
22
+ def select(proc = nil, &block)
23
+ return Maybe(@value.select(&block)) if @value.is_a?(::Enumerable)
24
+ return Nothing unless (proc || block).call(@value)
25
+ return self
26
+ end
27
+
40
28
  def truly?
41
29
  @value == true
42
30
  end
43
31
  end
44
32
 
45
33
  class Just < Maybe
46
- def initialize(value)
47
- @value = join(value)
48
- end
34
+ public_class_method :new
49
35
 
50
36
  def fetch(default=nil)
51
37
  @value
data/lib/monadic/monad.rb CHANGED
@@ -20,11 +20,22 @@ module Monadic
20
20
  end
21
21
 
22
22
  # Unwraps the the Monad
23
+ # @return the value contained in the monad
23
24
  def fetch
24
25
  @value
25
26
  end
26
27
  alias :_ :fetch
27
28
 
29
+ # A functor applying the proc or block on the boxed `value` and returning the Monad with the transformed values.
30
+ # If the underlying `value` is an `Enumerable`, the map is applied on each element of the collection.
31
+ # (A -> B) -> M[A] -> M[B]
32
+ def map(proc = nil, &block)
33
+ func = (proc || block)
34
+ return self.class.unit(@value.map {|v| func.call(v) }) if @value.is_a?(::Enumerable)
35
+ return self.class.unit(func.call(@value))
36
+ end
37
+
38
+ # Return the string representation of the Monad
28
39
  def to_s
29
40
  pretty_class_name = self.class.name.split('::')[-1]
30
41
  "#{pretty_class_name}(#{@value.nil? ? 'nil' : @value.to_s})"
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
data/lib/monadic.rb CHANGED
@@ -6,7 +6,4 @@ require 'monadic/maybe'
6
6
  require 'monadic/either'
7
7
  require 'monadic/validation'
8
8
 
9
- module Monadic
10
- end
11
-
12
9
  include Monadic
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'monadic/core_ext/object'
3
+
4
+ describe 'pseudo Elvis operator _?' do
5
+ it 'works' do
6
+ nil._?.should == Nothing
7
+ "foo"._?.should == 'foo'
8
+ {}._?.a.b.should == Nothing
9
+ {}._?[:foo] == Nothing
10
+ end
11
+ end
data/spec/monad_axioms.rb CHANGED
@@ -32,6 +32,26 @@ shared_examples 'a Monad' do
32
32
  end
33
33
 
34
34
  id1.should == id2
35
- end
35
+ end
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
36
56
  end
37
57
  end
data/spec/monad_spec.rb CHANGED
@@ -9,4 +9,16 @@ describe Monadic::Monad do
9
9
  Monad.unit(1).to_s.should == 'Monad(1)'
10
10
  Monad.unit(nil).to_s.should == 'Monad(nil)'
11
11
  end
12
+
13
+ describe '#map' do
14
+ it 'applies the function to the underlying value directly' do
15
+ Monad.unit(1).map {|v| v + 2}.should == Monad.unit(3)
16
+ Monad.unit('foo').map(&:upcase).should == Monad.unit('FOO')
17
+ end
18
+
19
+ it 'delegates #map to an underlying collection and wraps the resulting collection' do
20
+ Monad.unit([1,2]).map {|v| v + 1}.should == Monad.unit([2, 3])
21
+ Monad.unit(['foo', 'bar']).map(&:upcase).should == Monad.unit(['FOO', 'BAR'])
22
+ end
23
+ end
12
24
  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.6
4
+ version: 0.0.7
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-03 00:00:00.000000000 Z
12
+ date: 2012-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -140,6 +140,7 @@ files:
140
140
  - README.md
141
141
  - Rakefile
142
142
  - lib/monadic.rb
143
+ - lib/monadic/core_ext/object.rb
143
144
  - lib/monadic/either.rb
144
145
  - lib/monadic/errors.rb
145
146
  - lib/monadic/maybe.rb
@@ -147,6 +148,7 @@ files:
147
148
  - lib/monadic/validation.rb
148
149
  - lib/monadic/version.rb
149
150
  - monadic.gemspec
151
+ - spec/core_ext/object_spec.rb
150
152
  - spec/either_spec.rb
151
153
  - spec/jruby_fixes.rb
152
154
  - spec/maybe_spec.rb
@@ -179,6 +181,7 @@ signing_key:
179
181
  specification_version: 3
180
182
  summary: see README
181
183
  test_files:
184
+ - spec/core_ext/object_spec.rb
182
185
  - spec/either_spec.rb
183
186
  - spec/jruby_fixes.rb
184
187
  - spec/maybe_spec.rb