monadic 0.0.1 → 0.0.2
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/README.md +83 -2
- data/lib/monadic/option.rb +89 -0
- data/lib/monadic/version.rb +1 -1
- data/lib/monadic.rb +4 -4
- data/spec/option_spec.rb +104 -3
- data/spec/spec_helper.rb +2 -0
- metadata +2 -1
data/README.md
CHANGED
@@ -7,7 +7,7 @@ See also http://en.wikipedia.org/wiki/Monad
|
|
7
7
|
Planned are the following monadics:
|
8
8
|
|
9
9
|
- Option (Maybe in Haskell)
|
10
|
-
- Either
|
10
|
+
- Either *planned
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -25,7 +25,88 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
-
|
28
|
+
### Option
|
29
|
+
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'.
|
30
|
+
|
31
|
+
Basic usage examples:
|
32
|
+
|
33
|
+
# handling nil (None serves as NullObject)
|
34
|
+
obj = nil
|
35
|
+
Option(obj).a.b.c == None
|
36
|
+
|
37
|
+
# None stays None
|
38
|
+
Option(nil)._ == "None"
|
39
|
+
"#{Option(nil)}" == "None"
|
40
|
+
Option(nil)._("unknown") == "unknown"
|
41
|
+
Option(nil).none? == true
|
42
|
+
Option(nil).empty? == true
|
43
|
+
Option(nil).truly? == false
|
44
|
+
|
45
|
+
# Some stays Some, unless you unbox it
|
46
|
+
Option('FOO').downcase == Some('foo')
|
47
|
+
Option('FOO').downcase.value == "foo"
|
48
|
+
Option('FOO').downcase._ == "foo"
|
49
|
+
Option('foo').none? == false
|
50
|
+
Option('foo').empty? == false
|
51
|
+
Option('foo').truly? == true
|
52
|
+
|
53
|
+
Map, select:
|
54
|
+
|
55
|
+
Option(123).map { |value| User.find(value) } == Option(someUser) # if user found
|
56
|
+
Option(0).map { |value| User.find(value) } == None # if user not found
|
57
|
+
Option([1,2]).map { |value| value.to_s } == Option(["1", "2"]) # for all Enumerables
|
58
|
+
|
59
|
+
Option('foo').select { |value| value.start_with?('f') } == Some('foo')
|
60
|
+
Option('bar').select { |value| value.start_with?('f') } == None
|
61
|
+
|
62
|
+
Treat it like an array:
|
63
|
+
|
64
|
+
Option(123).to_a == [123]
|
65
|
+
Option([123, 456]).to_a == [123, 456]
|
66
|
+
Option(nil) == []
|
67
|
+
|
68
|
+
Falsey values (kind-of) examples:
|
69
|
+
|
70
|
+
user = Option(User.find(123))
|
71
|
+
user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
|
72
|
+
|
73
|
+
if user != nil
|
74
|
+
"You are logged in as foo"
|
75
|
+
else
|
76
|
+
"You are not logged in"
|
77
|
+
|
78
|
+
user.subscribed? # always true
|
79
|
+
user.subscribed?.truly? # true if subscribed is true
|
80
|
+
user.subscribed?.value(false) # same as above
|
81
|
+
user.subscribed?.or(false) # same as above
|
82
|
+
|
83
|
+
Remember! an Option is never false, if you want to know if it is false, call `#none?` of `#truly?`
|
84
|
+
|
85
|
+
`#truly?` will return true or false, always.
|
86
|
+
|
87
|
+
Slug example
|
88
|
+
|
89
|
+
# instead of
|
90
|
+
def slug(title)
|
91
|
+
if title
|
92
|
+
title.strip.downcase.tr_s('^[a-z0-9]', '-')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# or
|
97
|
+
|
98
|
+
def slug(title)
|
99
|
+
title && title.strip.downcase.tr_s('^[a-z0-9]', '-')
|
100
|
+
end
|
101
|
+
|
102
|
+
# do it with a default
|
103
|
+
def slug(title)
|
104
|
+
Option(title).strip.downcase.tr_s('^[a-z0-9]', '-')._('unknown-title')
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
see also [Option Type ](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/) and
|
109
|
+
[NullObject and Falsiness by @avdi](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
|
29
110
|
|
30
111
|
## Contributing
|
31
112
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
def Option(value)
|
4
|
+
return None if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
5
|
+
return Some.new(value)
|
6
|
+
end
|
7
|
+
alias :Some :Option
|
8
|
+
|
9
|
+
class Some
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
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
|
+
def to_ary
|
29
|
+
return [@value].flatten if @value.respond_to? :flatten
|
30
|
+
return [@value]
|
31
|
+
end
|
32
|
+
alias :to_a :to_ary
|
33
|
+
|
34
|
+
def map(&block)
|
35
|
+
return Option(@value.map(&block)) if @value.is_a?(Enumerable)
|
36
|
+
return Option(block.call)
|
37
|
+
end
|
38
|
+
|
39
|
+
def select(&block)
|
40
|
+
return Option(@value.select(&block)) if @value.is_a?(Enumerable)
|
41
|
+
return None unless block.call(@value)
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
|
45
|
+
def value(default=None, &block)
|
46
|
+
return default if none?
|
47
|
+
return block.call(@value) if block_given?
|
48
|
+
return @value
|
49
|
+
end
|
50
|
+
alias :or :value
|
51
|
+
alias :_ :value
|
52
|
+
|
53
|
+
def method_missing(m, *args)
|
54
|
+
Option(@value.__send__(m, *args))
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
return false unless other.is_a? Some
|
59
|
+
@value == other.instance_variable_get(:@value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class None
|
64
|
+
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
|
+
def to_ary
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
alias :to_a :to_ary
|
79
|
+
|
80
|
+
def none?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
alias :empty? :none?
|
84
|
+
|
85
|
+
def truly?
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/monadic/version.rb
CHANGED
data/lib/monadic.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
1
|
+
require 'monadic/version'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
$LOAD_PATH << File.expand_path('..', __FILE__)
|
4
|
+
|
5
|
+
require 'monadic/option'
|
data/spec/option_spec.rb
CHANGED
@@ -1,7 +1,108 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
describe 'Option' do
|
4
|
+
it 'nil as value always returns None()' do
|
5
|
+
Option(nil).a.b.c.should == None
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'None stays None' do
|
9
|
+
Option(nil)._.should == None
|
10
|
+
Option(nil).none?.should be_true
|
11
|
+
Option(nil).empty?.should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'Some stays Some' do
|
15
|
+
Option('foo').should be_kind_of(Some)
|
16
|
+
Option('foo').none?.should be_false
|
17
|
+
Option('foo').empty?.should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'None to_s is "None"' do
|
21
|
+
option = Option(nil)
|
22
|
+
"#{option}".should == "None"
|
23
|
+
"#{option._}".should == "None"
|
24
|
+
end
|
25
|
+
|
26
|
+
it '[] as value always returns None()' do
|
27
|
+
Option([]).a.should == None
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calling methods on Option always returns an Option with the transformed value' do
|
31
|
+
Option('FOO').downcase.should == Some('foo')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns the value of an option' do
|
35
|
+
Option('foo').value.should == 'foo'
|
36
|
+
Option('foo')._.should == 'foo'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns the value of an option with a default, in case value is None' do
|
40
|
+
Option(nil).value('bar').should == 'bar'
|
41
|
+
Option(nil)._('bar').should == 'bar'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns the value and not the default if it is Some' do
|
45
|
+
Option('FOO').downcase.value('bar').should == 'foo'
|
46
|
+
Option('FOO').downcase._('bar').should == 'foo'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns the value applied to a block if it is Some' do
|
50
|
+
Option('foo').value('bar') { |val| "You are logged in as #{val}" }.should == 'You are logged in as foo'
|
51
|
+
Option(nil).value('You are not logged in') { |val| "You are logged in as #{val}" }.should == 'You are not logged in'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'is never falsey' do
|
55
|
+
Option('foo').should_not be_false
|
56
|
+
Option(nil).should_not be_false
|
57
|
+
end
|
58
|
+
|
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
|
+
class User
|
65
|
+
attr_reader :name
|
66
|
+
def initialize(name)
|
67
|
+
@name = name
|
68
|
+
end
|
69
|
+
def subscribed?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'allows to use a block with value and _' do
|
75
|
+
user = Option(User.new('foo'))
|
76
|
+
user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'handles (kind-of) falsey values' do
|
80
|
+
user = Option(User.new('foo'))
|
81
|
+
user.subscribed?.or(false).should be_true
|
82
|
+
user.subscribed?.truly?.should be_true
|
83
|
+
|
84
|
+
user = Option(nil)
|
85
|
+
user.subscribed?.or(false).should be_false
|
86
|
+
user.subscribed?.truly?.should be_false
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'allows to use map' do
|
90
|
+
Option(nil).map { |e| Hash.new(:key => e) }.should == None
|
91
|
+
Option('foo').map { |e| Hash.new(:key => e) }.should == Some(Hash.new(:key => 'foo'))
|
92
|
+
Option([1,2]).map { |e| e.to_s }.should == Some(["1", "2"])
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'allows to use select' do
|
96
|
+
Option('foo').select { |e| e.start_with?('f') }.should == Some('foo')
|
97
|
+
Option('bar').select { |e| e.start_with?('f') }.should == None
|
98
|
+
Option(nil).select { |e| e.never_called }.should == None
|
99
|
+
Option([1, 2]).select { |e| e == 1 }.should == Some([1])
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'acts as an array' do
|
103
|
+
Option('foo').to_a.should == ['foo']
|
104
|
+
Option(['foo', 'bar']).to_a.should == ['foo', 'bar']
|
105
|
+
Option(nil).to_a.should == []
|
106
|
+
end
|
5
107
|
|
6
|
-
describe Option do
|
7
108
|
end
|
data/spec/spec_helper.rb
CHANGED
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.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- README.md
|
107
107
|
- Rakefile
|
108
108
|
- lib/monadic.rb
|
109
|
+
- lib/monadic/option.rb
|
109
110
|
- lib/monadic/version.rb
|
110
111
|
- monadic.gemspec
|
111
112
|
- spec/option_spec.rb
|