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 +14 -0
- data/README.md +40 -4
- data/lib/monadic/core_ext/object.rb +5 -0
- data/lib/monadic/either.rb +10 -7
- data/lib/monadic/maybe.rb +8 -22
- data/lib/monadic/monad.rb +11 -0
- data/lib/monadic/version.rb +1 -1
- data/lib/monadic.rb +0 -3
- data/spec/core_ext/object_spec.rb +11 -0
- data/spec/monad_axioms.rb +21 -1
- data/spec/monad_spec.rb +12 -0
- metadata +5 -2
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
|
-
|
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
|
-
|
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!
|
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
|
|
data/lib/monadic/either.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
85
|
-
include SuccessFailure::InstanceMethods
|
89
|
+
include SuccessFailure
|
86
90
|
end
|
87
91
|
|
88
92
|
class Failure < Either
|
89
|
-
|
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
|
-
|
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
|
-
|
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})"
|
data/lib/monadic/version.rb
CHANGED
data/lib/monadic.rb
CHANGED
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.
|
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-
|
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
|