wrapped 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +30 -0
- data/README.md +169 -0
- data/Rakefile +6 -0
- data/lib/wrapped.rb +2 -0
- data/lib/wrapped/blank.rb +66 -0
- data/lib/wrapped/injection.rb +22 -0
- data/lib/wrapped/present.rb +89 -0
- data/lib/wrapped/version.rb +3 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/wrapped_spec.rb +175 -0
- data/wrapped.gemspec +21 -0
- metadata +105 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Copyright (c)2011, Mike Burns
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above
|
12
|
+
copyright notice, this list of conditions and the following
|
13
|
+
disclaimer in the documentation and/or other materials provided
|
14
|
+
with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of Mike Burns nor the names of other
|
17
|
+
contributors may be used to endorse or promote products derived
|
18
|
+
from this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
24
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
25
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
26
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
28
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
29
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
Wrapped
|
2
|
+
-------
|
3
|
+
|
4
|
+
This gem is a tool you can use while developing your API to help consumers of
|
5
|
+
your code to find bugs earlier and faster. It works like this: any time you
|
6
|
+
write a method that could produce nil, you instead write a method that produces
|
7
|
+
a wrapped value.
|
8
|
+
|
9
|
+
Example
|
10
|
+
-------
|
11
|
+
|
12
|
+
Here's an example along with how it can help with errors:
|
13
|
+
|
14
|
+
Say you have a collection of users along with a method for accessing the first
|
15
|
+
user:
|
16
|
+
|
17
|
+
class UserCollection
|
18
|
+
def initialize(users)
|
19
|
+
@users = users
|
20
|
+
end
|
21
|
+
|
22
|
+
def first_user
|
23
|
+
@users.first
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Now your friend uses your awesome UserCollection code:
|
28
|
+
|
29
|
+
class FriendGroups
|
30
|
+
def initialize(user_collections)
|
31
|
+
@user_collections = user_collections
|
32
|
+
end
|
33
|
+
|
34
|
+
def first_names
|
35
|
+
@user_collections.map do |user_collection|
|
36
|
+
user_collection.first_user.first_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
And then she tries it:
|
42
|
+
|
43
|
+
FriendGroups.new([UserCollection.new([])]).first_names
|
44
|
+
|
45
|
+
... and it promptly blows up:
|
46
|
+
|
47
|
+
NoMethodError: undefined method `first_name' for nil:NilClass
|
48
|
+
from (irb):52:in `first_names'
|
49
|
+
from (irb):51:in `map'
|
50
|
+
from (irb):51:in `first_names'
|
51
|
+
from (irb):57
|
52
|
+
|
53
|
+
But that's odd; UserCollection definitely has a `#first_names` method, and we
|
54
|
+
definitely passed a UserCollection, and ... oooh, we passed no users, and so we
|
55
|
+
got `nil`.
|
56
|
+
|
57
|
+
Right.
|
58
|
+
|
59
|
+
Instead what you want to do is wrap it. Wrap that nil. Make the user know that
|
60
|
+
they have to consider the result.
|
61
|
+
|
62
|
+
class UserCollection
|
63
|
+
def initialize(users)
|
64
|
+
@users = users
|
65
|
+
end
|
66
|
+
|
67
|
+
def first_user
|
68
|
+
@users.first.wrapped
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Now in your documentation you explain that it produces a wrapped value. And
|
73
|
+
people who skip documentation and instead read source code will see that it is
|
74
|
+
wrapped.
|
75
|
+
|
76
|
+
So they unwrap it, because they must. They can't even get a happy path without
|
77
|
+
unwrapping it.
|
78
|
+
|
79
|
+
class FriendGroups
|
80
|
+
def initialize(user_collections)
|
81
|
+
@user_collections = user_collections
|
82
|
+
end
|
83
|
+
|
84
|
+
def first_names
|
85
|
+
@user_collections.map do |user_collection|
|
86
|
+
user_collection.first_user.unwrap_or('') {|user| user.first_name }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Cool Stuff
|
92
|
+
----------
|
93
|
+
|
94
|
+
A wrapped value mixes in Enumerable. The functional world would say "that's a
|
95
|
+
functor!". They're close enough.
|
96
|
+
|
97
|
+
This means that you can `map`, `inject`, `to_a`, `any?`, and so on over your
|
98
|
+
wrapped value. By wrapping it you've just made it more powerful!
|
99
|
+
|
100
|
+
For example:
|
101
|
+
|
102
|
+
irb(main):054:0> 1.wrapped.inject(0) {|_, n| n+1}
|
103
|
+
=> 2
|
104
|
+
irb(main):055:0> nil.wrapped.inject(0) {|_, n| n+1}
|
105
|
+
=> 0
|
106
|
+
|
107
|
+
And then we have `try`, which you can use to produce another wrapped object:
|
108
|
+
|
109
|
+
irb> 1.wrapped.try {|n| (n + 1).wrapped}.try {|n| (n*2).wrapped}.unwrap
|
110
|
+
=> 4
|
111
|
+
|
112
|
+
Those same people who will exclaim things about functors will, at this point,
|
113
|
+
get giddy about monads. I mean, they're right, but they can relax. It's just a
|
114
|
+
monad.
|
115
|
+
|
116
|
+
Those people ("what do you mean, 'those people'?!") may prefer the `fmap`
|
117
|
+
method:
|
118
|
+
|
119
|
+
irb> 1.wrapped.fmap {|n| n+1}.unwrap_or(0) {|n| n+4}
|
120
|
+
=> 6
|
121
|
+
|
122
|
+
Other Methods
|
123
|
+
-------------
|
124
|
+
|
125
|
+
Then we added some convenience methods to all of this. Here's a tour:
|
126
|
+
|
127
|
+
irb> require 'wrapped'
|
128
|
+
=> true
|
129
|
+
irb> 1.wrapped.unwrap_or(-1)
|
130
|
+
=> 1
|
131
|
+
irb> nil.wrapped.unwrap_or(-1)
|
132
|
+
=> -1
|
133
|
+
irb> 1.wrapped.present {|n| p n }.blank { puts "nothing!" }
|
134
|
+
1
|
135
|
+
=> #<Present:0x7fc570aed0e8 @value=1>
|
136
|
+
irb> nil.wrapped.present {|n| p n }.blank { puts "nothing!" }
|
137
|
+
nothing!
|
138
|
+
=> #<Blank:0x7fc570ae21c0>
|
139
|
+
irb> 1.wrapped.unwrap
|
140
|
+
=> 1
|
141
|
+
irb> nil.wrapped.unwrap
|
142
|
+
IndexError: Blank has no value
|
143
|
+
from /home/mike/wrapped/lib/wrapped/types.rb:43:in `unwrap'
|
144
|
+
from (irb):7
|
145
|
+
irb> 1.wrapped.present?
|
146
|
+
=> true
|
147
|
+
irb> nil.wrapped.present?
|
148
|
+
=> false
|
149
|
+
irb> nil.wrapped.blank?
|
150
|
+
=> true
|
151
|
+
irb> 1.wrapped.blank?
|
152
|
+
=> false
|
153
|
+
irb> 1.wrapped.unwrap_or(0) {|n| n * 100}
|
154
|
+
=> 100
|
155
|
+
irb> nil.wrapped.unwrap_or(0) {|n| n * 100}
|
156
|
+
=> 0
|
157
|
+
|
158
|
+
Inspiration
|
159
|
+
-----------
|
160
|
+
|
161
|
+
Inspired by a conversation about a post on the thoughtbot blog titled [If you
|
162
|
+
gaze into nil, nil gazes also into you](http://robots.thoughtbot.com/post/8181879506/if-you-gaze-into-nil-nil-gazes-also-into-you).
|
163
|
+
|
164
|
+
Most ideas are from Haskell and Scala. This is not new: look into the maybe
|
165
|
+
functor or the option class for more.
|
166
|
+
|
167
|
+
Copyright
|
168
|
+
---------
|
169
|
+
Copyright 2011 Mike Burns
|
data/Rakefile
ADDED
data/lib/wrapped.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# The class represents a lack of something.
|
2
|
+
class Blank
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# It is an error (specifically, an IndexError) to use this method.
|
6
|
+
def unwrap
|
7
|
+
raise IndexError.new("Blank has no value")
|
8
|
+
end
|
9
|
+
|
10
|
+
# Produce the value that is passed in.
|
11
|
+
#
|
12
|
+
# > w.unwrap_or(0)
|
13
|
+
def unwrap_or(default)
|
14
|
+
default
|
15
|
+
end
|
16
|
+
|
17
|
+
# Does nothing, returning itself. This is chainable. See blank for its
|
18
|
+
# companion.
|
19
|
+
#
|
20
|
+
# w.present.blank { puts "Missing" }
|
21
|
+
def present
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# Call the block then return itself. This is chainable. See present for its
|
26
|
+
# companion.
|
27
|
+
#
|
28
|
+
# w.blank { puts "I got nothing" }.present {|n| puts "got #{n}" }
|
29
|
+
def blank(&block)
|
30
|
+
block.call
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Produce the empty list.
|
35
|
+
#
|
36
|
+
# This class mixes in the Enumerable module, which relies on this.
|
37
|
+
#
|
38
|
+
# > w.each {|n| puts n }
|
39
|
+
def each
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
# False; this is not an instance of a wrapped value.
|
44
|
+
def present?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
# True; this is an instance of nothing.
|
49
|
+
def blank?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Do nothing, returning itself.
|
54
|
+
#
|
55
|
+
# > w.try {|n| n+1 }
|
56
|
+
def try
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Do nothing, returning itself.
|
61
|
+
#
|
62
|
+
# > w.fmap {|n| n+1 }
|
63
|
+
def fmap
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'wrapped/present'
|
2
|
+
require 'wrapped/blank'
|
3
|
+
|
4
|
+
class Object
|
5
|
+
# Wrap the object, forcing the user to be aware of the potential for a nil
|
6
|
+
# value by unwrapping it.
|
7
|
+
#
|
8
|
+
# See the Present class for details on how to unwrap it.
|
9
|
+
def wrapped
|
10
|
+
Present.new(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NilClass
|
15
|
+
# Wrap the nil, which is exactly the whole point. At this point the user must
|
16
|
+
# explictly deal with the nil, aware that it exists.
|
17
|
+
#
|
18
|
+
# See the Blank class and the README for details on how to work with this.
|
19
|
+
def wrapped
|
20
|
+
Blank.new
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# A class that represents a wrapped value. This class holds something that is
|
2
|
+
# not nil.
|
3
|
+
class Present
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
# Use Object#wrapped and NilClass#wrapped instead.
|
7
|
+
def initialize(value) # :nodoc:
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
# Produce the value, unwrapped.
|
12
|
+
#
|
13
|
+
# If a block is given apply the block to the value and produce that.
|
14
|
+
#
|
15
|
+
# > w.unwrap_or(0)
|
16
|
+
# > w.unwrap_or("hello") {|s| "Hi, #{s}" }
|
17
|
+
def unwrap_or(_)
|
18
|
+
if block_given?
|
19
|
+
yield unwrap
|
20
|
+
else
|
21
|
+
unwrap
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Invoke the block on the value, unwrapped. This method produces the wrapped
|
26
|
+
# value again, making it chainable. See `blank' for its companion.
|
27
|
+
#
|
28
|
+
# > w.present {|s| puts "Hello, #{s}" }.blank { puts "Do I know you?" }
|
29
|
+
def present(&block)
|
30
|
+
block.call(unwrap)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Do nothing then produce the wrapped value, making it chainable. See
|
35
|
+
# `present' for its companion.
|
36
|
+
#
|
37
|
+
# > w.blank { puts "Symbol not found" }.present {|s| puts users[s]}
|
38
|
+
def blank(&ignored)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Produce the singleton list with the unwrapped value as its only member.
|
43
|
+
#
|
44
|
+
# If a block is passed, it is run against the unwrapped value.
|
45
|
+
#
|
46
|
+
# This class mixes in Enumerable, which is controlled by this method.
|
47
|
+
#
|
48
|
+
# > w.each {|n| puts "Found #{n}" }
|
49
|
+
def each
|
50
|
+
yield unwrap if block_given?
|
51
|
+
[unwrap]
|
52
|
+
end
|
53
|
+
|
54
|
+
# The raw value. I doubt you need this method.
|
55
|
+
def unwrap
|
56
|
+
@value
|
57
|
+
end
|
58
|
+
|
59
|
+
# True; this is an instance of a wrapped value.
|
60
|
+
def present?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
# False; this does not wrap a nil.
|
65
|
+
def blank?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Run a block against the unwrapped value, producing the result of the block.
|
70
|
+
#
|
71
|
+
# > w.try {|n| n+1 }
|
72
|
+
#
|
73
|
+
# Also, you can use this like you would use >>= in Haskell. This and wrapped
|
74
|
+
# make it a monad.
|
75
|
+
#
|
76
|
+
# > w.try {|n| (n+1).wrapped }
|
77
|
+
def try
|
78
|
+
yield unwrap
|
79
|
+
end
|
80
|
+
|
81
|
+
# Run a block within the wrapper. This produces a wrapped value.
|
82
|
+
#
|
83
|
+
# > w.try {|n| n+1 }
|
84
|
+
#
|
85
|
+
# This makes it a functor.
|
86
|
+
def fmap
|
87
|
+
(yield unwrap).wrapped
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'wrapped'
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wrapped, 'conversion' do
|
4
|
+
let(:value) { 1 }
|
5
|
+
let(:just) { 1.wrapped }
|
6
|
+
let(:nothing) { nil.wrapped }
|
7
|
+
|
8
|
+
it "converts the value to a Present" do
|
9
|
+
just.should be_instance_of(Present)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "converts the nil to a Blank" do
|
13
|
+
nothing.should be_instance_of(Blank)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Wrapped, 'accessing' do
|
18
|
+
let(:value) { 1 }
|
19
|
+
let(:just) { 1.wrapped }
|
20
|
+
let(:nothing) { nil.wrapped }
|
21
|
+
|
22
|
+
it 'produces the value of the wrapped object' do
|
23
|
+
just.unwrap.should == value
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises an exception when called on the wrapped nil' do
|
27
|
+
expect { nothing.unwrap }.to raise_error(IndexError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Wrapped, 'callbacks' do
|
32
|
+
let(:value) { 1 }
|
33
|
+
let(:just) { 1.wrapped }
|
34
|
+
let(:nothing) { nil.wrapped }
|
35
|
+
|
36
|
+
it 'calls the proper callback for a wrapped value' do
|
37
|
+
result = false
|
38
|
+
just.present {|v| result = v}
|
39
|
+
result.should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'calls the proper callback for a wrapped nil' do
|
43
|
+
result = false
|
44
|
+
nothing.blank {result = true}
|
45
|
+
result.should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'ignores the other callback for a wrapped value' do
|
49
|
+
result = true
|
50
|
+
just.blank { result = false }
|
51
|
+
result.should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
it 'ignores the other callback for a wrapped nil' do
|
56
|
+
result = true
|
57
|
+
nothing.present { result = false }
|
58
|
+
result.should be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'chains for wrapped values' do
|
62
|
+
result = false
|
63
|
+
just.present { result = true }.blank { result = false }
|
64
|
+
result.should be_true
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'chains for wrapped nils' do
|
68
|
+
result = false
|
69
|
+
nothing.present { result = false }.blank { result = true }
|
70
|
+
result.should be_true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# This behavior is different from Haskell and Scala.
|
75
|
+
# It is done this way for consistency with Ruby.
|
76
|
+
# See the functor description later for `fmap'.
|
77
|
+
describe Wrapped, 'enumerable' do
|
78
|
+
let(:value) { 1 }
|
79
|
+
let(:just) { 1.wrapped }
|
80
|
+
let(:nothing) { nil.wrapped }
|
81
|
+
|
82
|
+
it 'acts over the value for #each on a wrapped value' do
|
83
|
+
result = -1
|
84
|
+
just.each {|v| result = v }
|
85
|
+
result.should == value
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'produces a singleton array of the value for a wrapped value on #each' do
|
89
|
+
just.each.should == [value]
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'skips the block for #each on a wrapped nil' do
|
93
|
+
result = -1
|
94
|
+
nothing.each {|v| result = v }
|
95
|
+
result.should == -1
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'produces the empty array for a wrapped nil on #each' do
|
99
|
+
nothing.each.should be_empty
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'maps over the value for a wrapped value' do
|
103
|
+
just.map {|n| n + 1}.should == [value+1]
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'map produces the empty list for a wrapped nil' do
|
107
|
+
nothing.map {|n| n + 1}.should == []
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe Wrapped, 'queries' do
|
112
|
+
let(:value) { 1 }
|
113
|
+
let(:just) { 1.wrapped }
|
114
|
+
let(:nothing) { nil.wrapped }
|
115
|
+
|
116
|
+
it 'knows whether it is present' do
|
117
|
+
just.should be_present
|
118
|
+
nothing.should_not be_present
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'knows whether it is blank' do
|
122
|
+
just.should_not be_blank
|
123
|
+
nothing.should be_blank
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe Wrapped, 'unwrap_or' do
|
128
|
+
let(:value) { 1 }
|
129
|
+
let(:just) { 1.wrapped }
|
130
|
+
let(:nothing) { nil.wrapped }
|
131
|
+
|
132
|
+
it 'produces the value for a wrapped value' do
|
133
|
+
just.unwrap_or(-1).should == value
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'produces the default for a wrapped nil' do
|
137
|
+
nothing.unwrap_or(-1).should == -1
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'produces the value of the block for a wrapped object' do
|
141
|
+
just.unwrap_or(-1) {|n| n+1}.should == value + 1
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'produces the default for a wrapped nil even with a block' do
|
145
|
+
nothing.unwrap_or(-1) {2}.should == -1
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe Wrapped, 'monadic' do
|
150
|
+
let(:value) { 1 }
|
151
|
+
let(:just) { 1.wrapped }
|
152
|
+
let(:nothing) { nil.wrapped }
|
153
|
+
|
154
|
+
it 'produces the value from #try for a wrapped value' do
|
155
|
+
just.try {|n| (n+1).wrapped }.unwrap.should == value+1
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'produces blank from #try for a wrapped nil' do
|
159
|
+
nothing.try {|n| (n+1).wrapped}.should be_blank
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe Wrapped, 'functor' do
|
164
|
+
let(:value) { 1 }
|
165
|
+
let(:just) { 1.wrapped }
|
166
|
+
let(:nothing) { nil.wrapped }
|
167
|
+
|
168
|
+
it 'unwraps, applies the block, then re-wraps for a wrapped value' do
|
169
|
+
just.fmap {|n| n+1}.unwrap.should == value+1
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'produces the blank for a wrapped nil' do
|
173
|
+
nothing.fmap {|n| n+1}.should be_blank
|
174
|
+
end
|
175
|
+
end
|
data/wrapped.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "wrapped/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "wrapped"
|
7
|
+
s.version = Wrapped::VERSION
|
8
|
+
s.authors = ["Mike Burns"]
|
9
|
+
s.email = ["mike@mike-burns.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{The maybe functor for Ruby}
|
12
|
+
s.description = %q{The unchecked nil is a dangerous concept leading to NoMethodErrors at runtime. It would be better if you were forced to explictly unwrap potentially nil values. This library provides mechanisms and convenience methods for making this more possible.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency('rspec')
|
20
|
+
s.add_development_dependency('rake')
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wrapped
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mike Burns
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-29 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: The unchecked nil is a dangerous concept leading to NoMethodErrors at runtime. It would be better if you were forced to explictly unwrap potentially nil values. This library provides mechanisms and convenience methods for making this more possible.
|
49
|
+
email:
|
50
|
+
- mike@mike-burns.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- lib/wrapped.rb
|
64
|
+
- lib/wrapped/blank.rb
|
65
|
+
- lib/wrapped/injection.rb
|
66
|
+
- lib/wrapped/present.rb
|
67
|
+
- lib/wrapped/version.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- spec/wrapped_spec.rb
|
70
|
+
- wrapped.gemspec
|
71
|
+
homepage: ""
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.7.2
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: The maybe functor for Ruby
|
104
|
+
test_files: []
|
105
|
+
|