monadic 0.0.2 → 0.0.3
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 +11 -0
- data/README.md +45 -19
- data/lib/monadic/option.rb +43 -39
- data/lib/monadic/version.rb +1 -1
- data/monadic.gemspec +1 -0
- data/spec/option_spec.rb +31 -8
- metadata +19 -2
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## v0.0.3
|
4
|
+
|
5
|
+
`Some#map` and `Some#select` accept proc and block, you can now use:
|
6
|
+
|
7
|
+
Option("FOO").map(&:downcase) # NEW
|
8
|
+
Option("FOO").map { |e| e.downcase } # old
|
9
|
+
Option("FOO").downcase # old
|
10
|
+
|
11
|
+
Removed `#none?`, please use `#empty?` instead.
|
data/README.md
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
# Monadic
|
2
2
|
|
3
|
-
|
3
|
+
helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in Scala to my ruby projects (hence I will be using more Scala like constructs than Haskell).
|
4
4
|
|
5
5
|
See also http://en.wikipedia.org/wiki/Monad
|
6
6
|
|
7
|
-
|
7
|
+
We have the following monadics:
|
8
8
|
|
9
9
|
- Option (Maybe in Haskell)
|
10
10
|
- Either *planned
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
Add this line to your application's Gemfile:
|
15
|
-
|
16
|
-
gem 'monadic'
|
12
|
+
What's the point of using monads in ruby? To me it started with having a safe way to deal with nil objects and other exceptions.
|
13
|
+
Thus you contain the erroneous behaviour within a monad - an indivisible, impenetrable unit.
|
17
14
|
|
18
|
-
|
15
|
+
## Usage
|
19
16
|
|
20
|
-
|
17
|
+
### Option
|
18
|
+
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'.
|
21
19
|
|
22
|
-
Or install it yourself as:
|
23
20
|
|
24
|
-
|
21
|
+
Option(User.find(123)).name._ # ._ is a shortcut for .value
|
25
22
|
|
26
|
-
|
23
|
+
# if you prefer the alias Maybe instead of option
|
24
|
+
Maybe(User.find(123)).name._
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
# confidently diving into nested hashes
|
27
|
+
Maybe({})[:a][:b][:c] == None
|
28
|
+
Maybe({})[:a][:b][:c].value('unknown') == None
|
29
|
+
Maybe(a: 1)[:a]._ == 1
|
30
30
|
|
31
31
|
Basic usage examples:
|
32
32
|
|
@@ -38,7 +38,6 @@ Basic usage examples:
|
|
38
38
|
Option(nil)._ == "None"
|
39
39
|
"#{Option(nil)}" == "None"
|
40
40
|
Option(nil)._("unknown") == "unknown"
|
41
|
-
Option(nil).none? == true
|
42
41
|
Option(nil).empty? == true
|
43
42
|
Option(nil).truly? == false
|
44
43
|
|
@@ -46,7 +45,6 @@ Basic usage examples:
|
|
46
45
|
Option('FOO').downcase == Some('foo')
|
47
46
|
Option('FOO').downcase.value == "foo"
|
48
47
|
Option('FOO').downcase._ == "foo"
|
49
|
-
Option('foo').none? == false
|
50
48
|
Option('foo').empty? == false
|
51
49
|
Option('foo').truly? == true
|
52
50
|
|
@@ -68,6 +66,8 @@ Treat it like an array:
|
|
68
66
|
Falsey values (kind-of) examples:
|
69
67
|
|
70
68
|
user = Option(User.find(123))
|
69
|
+
user.name._
|
70
|
+
|
71
71
|
user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
|
72
72
|
|
73
73
|
if user != nil
|
@@ -80,7 +80,7 @@ Falsey values (kind-of) examples:
|
|
80
80
|
user.subscribed?.value(false) # same as above
|
81
81
|
user.subscribed?.or(false) # same as above
|
82
82
|
|
83
|
-
Remember! an Option is never false, if you want to know if it is false, call `#
|
83
|
+
Remember! an Option is never false (in Ruby terms), if you want to know if it is false, call `#empty?` of `#truly?`
|
84
84
|
|
85
85
|
`#truly?` will return true or false, always.
|
86
86
|
|
@@ -105,8 +105,34 @@ Slug example
|
|
105
105
|
end
|
106
106
|
|
107
107
|
|
108
|
-
see also
|
109
|
-
|
108
|
+
see also
|
109
|
+
|
110
|
+
* [Monad equivalend in Ruby](http://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby)
|
111
|
+
* [Option Type ](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
|
112
|
+
* [NullObject and Falsiness by @avdi](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
|
113
|
+
* [andand](https://github.com/raganwald/andand/blob/master/README.textile)
|
114
|
+
* [ick](http://ick.rubyforge.org/)
|
115
|
+
* [Monads in Ruby](http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html)
|
116
|
+
* [The Maybe Monad in Ruby](http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in-ruby/)
|
117
|
+
* [Monads in Ruby with nice syntax](http://www.valuedlessons.com/2008/01/monads-in-ruby-with-nice-syntax.html)
|
118
|
+
* [Maybe in Ruby](https://github.com/bhb/maybe)
|
119
|
+
* [Monads on the Cheap](http://osteele.com/archives/2007/12/cheap-monads)
|
120
|
+
* [Rumonade](https://github.com/ms-ati/rumonade)
|
121
|
+
* [Monads for functional programming](http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf)
|
122
|
+
|
123
|
+
## Installation
|
124
|
+
|
125
|
+
Add this line to your application's Gemfile:
|
126
|
+
|
127
|
+
gem 'monadic'
|
128
|
+
|
129
|
+
And then execute:
|
130
|
+
|
131
|
+
$ bundle
|
132
|
+
|
133
|
+
Or install it yourself as:
|
134
|
+
|
135
|
+
$ gem install monadic
|
110
136
|
|
111
137
|
## Contributing
|
112
138
|
|
data/lib/monadic/option.rb
CHANGED
@@ -1,89 +1,93 @@
|
|
1
1
|
require 'singleton'
|
2
|
+
# Represents optional values. Instances of Option are either an instance of Some or the object None.
|
3
|
+
#
|
2
4
|
|
5
|
+
# Helper function which returns Some or None respectively, depending on their value
|
6
|
+
# I find this moar simplistic in ruby than the traditional #bind and #unit
|
3
7
|
def Option(value)
|
4
8
|
return None if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
5
9
|
return Some.new(value)
|
6
10
|
end
|
7
|
-
alias :Some
|
11
|
+
alias :Some :Option
|
12
|
+
alias :Maybe :Option
|
8
13
|
|
9
|
-
|
14
|
+
# Represents the Option if there is some value available
|
15
|
+
class Some
|
10
16
|
def initialize(value)
|
11
17
|
@value = value
|
12
18
|
end
|
13
19
|
|
14
|
-
def none?
|
15
|
-
false
|
16
|
-
end
|
17
|
-
alias :empty? :none?
|
18
|
-
|
19
|
-
def truly?
|
20
|
-
@value == true
|
21
|
-
end
|
22
|
-
|
23
|
-
def else(default)
|
24
|
-
return default if none?
|
25
|
-
return self
|
26
|
-
end
|
27
|
-
|
28
20
|
def to_ary
|
29
21
|
return [@value].flatten if @value.respond_to? :flatten
|
30
22
|
return [@value]
|
31
23
|
end
|
32
24
|
alias :to_a :to_ary
|
33
25
|
|
34
|
-
def
|
35
|
-
|
36
|
-
return Option(block.call)
|
26
|
+
def empty?
|
27
|
+
false
|
37
28
|
end
|
38
29
|
|
39
|
-
def
|
40
|
-
|
41
|
-
return None unless block.call(@value)
|
42
|
-
return self
|
30
|
+
def truly?
|
31
|
+
@value == true
|
43
32
|
end
|
44
33
|
|
45
34
|
def value(default=None, &block)
|
46
|
-
return default if
|
35
|
+
return default if empty?
|
47
36
|
return block.call(@value) if block_given?
|
48
37
|
return @value
|
49
|
-
end
|
38
|
+
end
|
50
39
|
alias :or :value
|
51
40
|
alias :_ :value
|
52
41
|
|
42
|
+
def map(func = nil, &block)
|
43
|
+
return Option(@value.map(&block)) if @value.is_a?(Enumerable)
|
44
|
+
return Option((func || block).call(@value))
|
45
|
+
end
|
46
|
+
|
53
47
|
def method_missing(m, *args)
|
54
48
|
Option(@value.__send__(m, *args))
|
55
|
-
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def select(func = nil, &block)
|
52
|
+
return Option(@value.select(&block)) if @value.is_a?(Enumerable)
|
53
|
+
return None unless (func || block).call(@value)
|
54
|
+
return self
|
55
|
+
end
|
56
56
|
|
57
57
|
def ==(other)
|
58
58
|
return false unless other.is_a? Some
|
59
59
|
@value == other.instance_variable_get(:@value)
|
60
|
-
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"Some(#{@value.to_s})"
|
64
|
+
end
|
61
65
|
end
|
62
66
|
|
67
|
+
# Represents the Option if there is no value available
|
63
68
|
class None
|
64
69
|
class << self
|
65
|
-
def method_missing(m, *args)
|
66
|
-
self
|
67
|
-
end
|
68
|
-
|
69
|
-
def value(default=self)
|
70
|
-
default
|
71
|
-
end
|
72
|
-
alias :or :value
|
73
|
-
alias :_ :value
|
74
|
-
|
75
70
|
def to_ary
|
76
71
|
[]
|
77
72
|
end
|
78
73
|
alias :to_a :to_ary
|
79
74
|
|
80
|
-
def
|
75
|
+
def empty?
|
81
76
|
true
|
82
77
|
end
|
83
|
-
|
78
|
+
|
79
|
+
def method_missing(m, *args)
|
80
|
+
self
|
81
|
+
end
|
84
82
|
|
85
83
|
def truly?
|
86
84
|
false
|
87
85
|
end
|
86
|
+
|
87
|
+
def value(default=self)
|
88
|
+
default
|
89
|
+
end
|
90
|
+
alias :or :value
|
91
|
+
alias :_ :value
|
88
92
|
end
|
89
93
|
end
|
data/lib/monadic/version.rb
CHANGED
data/monadic.gemspec
CHANGED
data/spec/option_spec.rb
CHANGED
@@ -7,22 +7,29 @@ describe 'Option' do
|
|
7
7
|
|
8
8
|
it 'None stays None' do
|
9
9
|
Option(nil)._.should == None
|
10
|
-
Option(nil).none?.should be_true
|
11
10
|
Option(nil).empty?.should be_true
|
12
11
|
end
|
13
12
|
|
14
13
|
it 'Some stays Some' do
|
15
14
|
Option('foo').should be_kind_of(Some)
|
16
|
-
Option('foo').none?.should be_false
|
17
15
|
Option('foo').empty?.should be_false
|
18
16
|
end
|
19
17
|
|
20
|
-
it '
|
18
|
+
it 'Some#to_s is "Some(value)"' do
|
19
|
+
Some(123).to_s.should == "Some(123)"
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'None#to_s is "None"' do
|
21
23
|
option = Option(nil)
|
22
24
|
"#{option}".should == "None"
|
23
25
|
"#{option._}".should == "None"
|
24
26
|
end
|
25
27
|
|
28
|
+
it 'None is always empty' do
|
29
|
+
None.empty?.should be_true
|
30
|
+
Maybe(nil).empty?.should be_true
|
31
|
+
end
|
32
|
+
|
26
33
|
it '[] as value always returns None()' do
|
27
34
|
Option([]).a.should == None
|
28
35
|
end
|
@@ -56,11 +63,6 @@ describe 'Option' do
|
|
56
63
|
Option(nil).should_not be_false
|
57
64
|
end
|
58
65
|
|
59
|
-
it 'tells you if it is none when calling #none?' do
|
60
|
-
Option('foo').none?.should be_false
|
61
|
-
Option(nil).none?.should be_true
|
62
|
-
end
|
63
|
-
|
64
66
|
class User
|
65
67
|
attr_reader :name
|
66
68
|
def initialize(name)
|
@@ -105,4 +107,25 @@ describe 'Option' do
|
|
105
107
|
Option(nil).to_a.should == []
|
106
108
|
end
|
107
109
|
|
110
|
+
it 'diving into hashes' do
|
111
|
+
Maybe({})['a']['b']['c'].should == None
|
112
|
+
Maybe({a: 1})[:a]._.should == 1
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should support Rumonades example' do
|
116
|
+
require 'active_support/time'
|
117
|
+
def format_date_in_march(time_or_date_or_nil)
|
118
|
+
Option(time_or_date_or_nil). # wraps possibly-nil value in an Option monad (Some or None)
|
119
|
+
map(&:to_date). # transforms a contained Time value into a Date value
|
120
|
+
select {|d| d.month == 3}. # filters out non-matching Date values (Some becomes None)
|
121
|
+
map(&:to_s). # transforms a contained Date value into a String value
|
122
|
+
map {|s| s.gsub('-', '')}. # transforms a contained String value by removing '-'
|
123
|
+
value("not in march!") # returns the contained value, or the alternative if None
|
124
|
+
end
|
125
|
+
|
126
|
+
format_date_in_march(nil).should == "not in march!"
|
127
|
+
format_date_in_march(Time.parse('2009-01-01 01:02')).should == "not in march!"
|
128
|
+
format_date_in_march(Time.parse('2011-03-21 12:34')).should == "20110321"
|
129
|
+
end
|
130
|
+
|
108
131
|
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.3
|
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-04-
|
12
|
+
date: 2012-04-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -91,6 +91,22 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: activesupport
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
94
110
|
description: brings some functional goodness to ruby
|
95
111
|
email:
|
96
112
|
- pz@anixe.pl
|
@@ -100,6 +116,7 @@ extra_rdoc_files: []
|
|
100
116
|
files:
|
101
117
|
- .gitignore
|
102
118
|
- .rspec
|
119
|
+
- CHANGELOG.md
|
103
120
|
- Gemfile
|
104
121
|
- Guardfile
|
105
122
|
- LICENSE
|