maybe 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/LICENSE +16 -14
  2. data/README.md +86 -0
  3. data/README.rdoc +28 -17
  4. data/VERSION +1 -1
  5. data/lib/maybe.rb +61 -28
  6. data/test/maybe_test.rb +269 -86
  7. metadata +23 -10
data/LICENSE CHANGED
@@ -1,20 +1,22 @@
1
- Copyright (c) 2009 Ben Brinckerhoff
1
+ Copyright (c) 2009, 2010 Ben Brinckerhoff
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
10
11
 
11
12
  The above copyright notice and this permission notice shall be
12
13
  included in all copies or substantial portions of the Software.
13
14
 
14
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ maybe
2
+ =====
3
+
4
+ A library for treating nil and non-nil objects in a similar manner. Technically speaking, Maybe is an implemenation of the maybe monad.
5
+
6
+ Synopsis
7
+ --------
8
+
9
+ The Maybe class wraps any value (nil or non-nil) and lets you treat it as non-nil.
10
+
11
+ "hello".upcase #=> "HELLO"
12
+ nil.upcase #=> NoMethodError: undefined method `upcase' for nil:NilClass
13
+ Maybe.new("hello").upcase.__value__ #=> "HELLO"
14
+ Maybe.new(nil).upcase.__value__ #=> nil
15
+
16
+ You can also use the method `Maybe` for convenience. The following are equivalent:
17
+
18
+ Maybe.new("hello").__value__ #=> "hello"
19
+ Maybe("hello").__value__ #=> "hello"
20
+
21
+ When you call `Maybe.new` with a value, that value is wrapped in a Maybe object. Whenever you call methods on that object, it does a simple check: if the wrapped value is nil, then it returns another Maybe object that wraps nil. If the wrapped object is not nil, it calls the method on that object, then wraps it back up in a Maybe object.
22
+
23
+ This is especially handy for long chains of method calls, any of which could return nil.
24
+
25
+ # foo, bar, and/or baz could return nil, but this will still work
26
+ Maybe.new(foo).bar(1).baz(:x)
27
+
28
+ Here's a real world example. Instead of writing this:
29
+
30
+ if(customer && customer.order && customer.order.id==newest_customer_id)
31
+ # ... do something with customer
32
+ end
33
+
34
+ just write this:
35
+
36
+ if(Maybe.new(customer).order.id.__value__==newest_customer_id)
37
+ # ... do something with customer
38
+ end
39
+
40
+ If your wrapped object does not have a `#value` method, you can call
41
+
42
+ Maybe.new(obj).value
43
+
44
+ instead of
45
+
46
+ Maybe.new(obj).__value__
47
+
48
+ Examples
49
+ --------
50
+
51
+ Maybe.new("10") #=> A Maybe object, wrapping "10"
52
+
53
+ Maybe.new("10").to_i #=> A Maybe object, wrapping 10
54
+
55
+ Maybe.new("10").to_i.__value__ #=> 10
56
+
57
+ Maybe.new(nil) #=> A Maybe object, wrapping nil
58
+
59
+ Maybe.new(nil).to_i #=> A Maybe object, still wrapping nil
60
+
61
+ Maybe.new(nil).to_i.__value__ #=> nil
62
+
63
+ Related Reading
64
+ ---------------
65
+
66
+ * [MenTaLguY has a great tutorial on Monads in Ruby over at Moonbase](http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html)
67
+ * [Oliver Steele explores the problem in depth and looks at a number of different solutions](http://osteele.com/archives/2007/12/cheap-monads)
68
+ * [Reg Braithwaite explores this same problem and comes up with a different, but very cool solution in Ruby](http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html)
69
+ * [Weave Jester has another solution, inspired by the Maybe monad](http://weavejester.com/node/10)
70
+
71
+ Note on Patches/Pull Requests
72
+ ------
73
+
74
+ * Fork the project.
75
+ * Make your feature addition or bug fix.
76
+ * Add tests for it. This is important so I don't break it in a
77
+ future version unintentionally.
78
+ * Commit, do not mess with rakefile, version, or history.
79
+ (if you want to have your own version, that is fine but
80
+ bump version in a commit by itself I can ignore when I pull)
81
+ * Send me a pull request. Bonus points for topic branches.
82
+
83
+ Copyright
84
+ ----
85
+
86
+ Copyright (c) 2009, 2010 Ben Brinckerhoff. See LICENSE for details.
data/README.rdoc CHANGED
@@ -1,20 +1,22 @@
1
- = maybe
1
+ maybe
2
+ =====
2
3
 
3
4
  A library for treating nil and non-nil objects in a similar manner. Technically speaking, Maybe is an implemenation of the maybe monad.
4
5
 
5
- == Synopsis
6
+ Synopsis
7
+ --------
6
8
 
7
9
  The Maybe class wraps any value (nil or non-nil) and lets you treat it as non-nil.
8
10
 
9
- "hello".upcase #=> "HELLO"
10
- nil.upcase #=> NoMethodError: undefined method `upcase' for nil:NilClass
11
- Maybe.new("hello").upcase.value #=> "HELLO"
12
- Maybe.new(nil).upcase.value #=> nil
11
+ "hello".upcase #=> "HELLO"
12
+ nil.upcase #=> NoMethodError: undefined method `upcase' for nil:NilClass
13
+ Maybe.new("hello").upcase.__value__ #=> "HELLO"
14
+ Maybe.new(nil).upcase.__value__ #=> nil
13
15
 
14
16
  You can also use the method Maybe for convenience. The following are equivalent:
15
17
 
16
- Maybe.new("hello").value #=> "hello"
17
- Maybe("hello").value #=> "hello"
18
+ Maybe.new("hello").__value__ #=> "hello"
19
+ Maybe("hello").__value__ #=> "hello"
18
20
 
19
21
  When you call Maybe.new with a value, that value is wrapped in a Maybe object. Whenever you call methods on that object, it does a simple check: if the wrapped value is nil, then it returns another Maybe object that wraps nil. If the wrapped object is not nil, it calls the method on that object, then wraps it back up in a Maybe object.
20
22
 
@@ -31,23 +33,32 @@ Here's a real world example. Instead of writing this:
31
33
 
32
34
  just write this:
33
35
 
34
- if(Maybe.new(customer).order.id.value==newest_customer_id)
36
+ if(Maybe.new(customer).order.id.__value__==newest_customer_id)
35
37
  # ... do something with customer
36
38
  end
37
39
 
38
- == Examples
40
+ If your wrapped object does not have a `#value` method, you can call
39
41
 
40
- Maybe.new("10") #=> A Maybe object, wrapping "10"
42
+ Maybe.new(obj).value
41
43
 
42
- Maybe.new("10").to_i #=> A Maybe object, wrapping 10
44
+ instead of
43
45
 
44
- Maybe.new("10").to_i.value #=> 10
46
+ Maybe.new(obj).__value__
45
47
 
46
- Maybe.new(nil) #=> A Maybe object, wrapping nil
48
+ Examples
49
+ --------
47
50
 
48
- Maybe.new(nil).to_i #=> A Maybe object, still wrapping nil
51
+ Maybe.new("10") #=> A Maybe object, wrapping "10"
49
52
 
50
- Maybe.new(nil).to_i.value #=> nil
53
+ Maybe.new("10").to_i #=> A Maybe object, wrapping 10
54
+
55
+ Maybe.new("10").to_i.__value__ #=> 10
56
+
57
+ Maybe.new(nil) #=> A Maybe object, wrapping nil
58
+
59
+ Maybe.new(nil).to_i #=> A Maybe object, still wrapping nil
60
+
61
+ Maybe.new(nil).to_i.__value__ #=> nil
51
62
 
52
63
  == Related Reading
53
64
 
@@ -64,7 +75,7 @@ just write this:
64
75
  future version unintentionally.
65
76
  * Commit, do not mess with rakefile, version, or history.
66
77
  (if you want to have your own version, that is fine but
67
- bump version in a commit by itself I can ignore when I pull)
78
+ bump version in a commit by itself I can ignore when I pull)
68
79
  * Send me a pull request. Bonus points for topic branches.
69
80
 
70
81
  == Copyright
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 1.0.0
data/lib/maybe.rb CHANGED
@@ -4,61 +4,94 @@ end
4
4
 
5
5
  class Maybe
6
6
 
7
- instance_methods.reject { |method_name| method_name =~ /^__/ }.each { |method_name| undef_method method_name }
7
+ LEGACY_METHODS = %w{value pass fmap join}
8
8
 
9
+ instance_methods.reject { |method_name| method_name.to_s =~ /^__/ || ['object_id','respond_to?', 'methods'].include?(method_name.to_s) }.each { |method_name| undef_method method_name }
10
+
11
+ # Initializes a new Maybe object
12
+ # @param value Any Ruby object (nil or non-nil)
9
13
  def initialize(value)
10
14
  @value = value
11
- self.join
15
+ __join__
16
+ end
17
+
18
+ def respond_to?(method_name)
19
+ return true if LEGACY_METHODS.include?(method_name.to_s)
20
+ super || @value.respond_to?(method_name)
21
+ end
22
+
23
+ def methods
24
+ super + @value.methods + LEGACY_METHODS
25
+ end
26
+
27
+ def respond_to_missing?(method_name, *args, &block)
28
+ # For Ruby 1.9 support
29
+ super
12
30
  end
13
31
 
14
- def method_missing(method_name, *args)
15
- self.fmap do |value|
16
- value.send(method_name,*args) do |*block_args|
17
- yield(*block_args) if block_given?
32
+ def method_missing(method_name, *args, &block)
33
+ if LEGACY_METHODS.include?(method_name.to_s)
34
+ if @value.respond_to?(method_name)
35
+ @value.send(method_name, *args, &block)
36
+ else
37
+ __send__("__#{method_name}__", *args, &block)
38
+ end
39
+ else
40
+ __fmap__ do |value|
41
+ value.send(method_name,*args) do |*block_args|
42
+ yield(*block_args) if block_given?
43
+ end
18
44
  end
19
45
  end
20
46
  end
21
-
22
- def value(value_if_nil=nil)
23
- if(value_if_nil!=nil && @value==nil)
47
+
48
+ # Unwraps the Maybe object. If the wrapped object does not define #value
49
+ # you may call #value instead of \_\_value\_\_
50
+ # @param value_if_nil A value to return if the wrapped value is nil.
51
+ # @return the wrapped object
52
+ def __value__(value_if_nil=nil)
53
+ if(@value==nil)
24
54
  value_if_nil
25
55
  else
26
56
  @value
27
57
  end
28
58
  end
29
-
30
- # Given that the value is of type A
59
+
60
+ def nil?
61
+ @value==nil
62
+ end
63
+
64
+ # Only included to provide a complete Monad interface. Not recommended
65
+ # for general use.
66
+ # (Technically: Given that the value is of type A
31
67
  # takes a function from A->M[B] and returns
32
- # M[B] (a monad with a value of type B)
33
- def pass
34
- fmap {|*block_args| yield(*block_args)}.join
68
+ # M[B] (a monad with a value of type B))
69
+ def __pass__
70
+ __fmap__ {|*block_args| yield(*block_args)}.__join__
35
71
  end
36
72
 
37
- # Given that the value is of type A
73
+ # Only included to provide a complete Monad interface. Not recommended
74
+ # for general use.
75
+ # (Technically: Given that the value is of type A
38
76
  # takes a function from A->B and returns
39
- # M[B] (a monad with a value of type B)
40
- def fmap
77
+ # M[B] (a monad with a value of type B))
78
+ def __fmap__
41
79
  if(@value==nil)
42
80
  self
43
81
  else
44
82
  Maybe.new(yield(@value))
45
83
  end
46
84
  end
47
-
48
- def nil?
49
- @value==nil
50
- end
51
85
 
52
- def join
86
+ # Only included to provide a complete Monad interface. Not recommended
87
+ # for general use.
88
+ # (Technically: M[M[A]] is equivalent to M[A], that is, monads should be flat
89
+ # rather than nested)
90
+ def __join__
53
91
  if(@value.is_a?(Maybe))
54
- @value = value.value
92
+ @value = @value.__value__
55
93
  end
56
94
  self
57
95
  end
58
96
 
59
- # for testing purposes
60
- def value=(value)
61
- @value = value
62
- end
63
-
64
97
  end
data/test/maybe_test.rb CHANGED
@@ -1,122 +1,305 @@
1
1
  require File.expand_path("test_helper", File.dirname(__FILE__))
2
2
  require 'cgi'
3
+ require 'shoulda'
3
4
 
4
5
  class MaybeTest < Test::Unit::TestCase
5
6
 
6
- def test_initialize__performs_join
7
- assert_equal 1, Maybe.new(Maybe.new(1)).value
8
- end
7
+ context "#initialize" do
9
8
 
10
- def test_initialize__never_calls_pass_on_nested_maybe
11
- Maybe.any_instance.expects(:pass).never
12
- Maybe.new(Maybe.new(1)).value
13
- end
9
+ should "perform join" do
10
+ assert_equal 1, Maybe.new(Maybe.new(1)).__value__
11
+ end
14
12
 
15
- def test_call_match_operator
16
- assert_equal nil, (Maybe.new(nil)=~/b/).value
17
- assert_equal 1, (Maybe.new('abc')=~/b/).value
13
+ should "never call pass on nested maybe" do
14
+ Maybe.any_instance.expects(:__pass__).never
15
+ Maybe.new(Maybe.new(1)).__value__
16
+ end
17
+
18
18
  end
19
19
 
20
- def test_call_method_defined_on_object
21
- assert_equal nil, (Maybe.new(nil).to_s).value
22
- assert_equal "1", (Maybe.new(1).to_s).value
23
- end
20
+ context "when calling methods" do
21
+
22
+ should "return correct value for match operator" do
23
+ assert_equal nil, (Maybe.new(nil)=~/b/).__value__
24
+ assert_equal 1, (Maybe.new('abc')=~/b/).__value__
25
+ end
26
+
27
+ should "return correct value for to_s" do
28
+ assert_equal nil, (Maybe.new(nil).to_s).__value__
29
+ assert_equal "1", (Maybe.new(1).to_s).__value__
30
+ end
31
+
32
+ should "return correct value for to_int" do
33
+ assert_equal nil, Maybe.new(nil).to_int.__value__
34
+ assert_equal 2, Maybe.new(2.3).to_int.__value__
35
+ end
36
+
37
+ should "work if method call takes a block" do
38
+ assert_equal nil, Maybe.new(nil).map{|x|x*2}.__value__
39
+ assert_equal [2,4,6], Maybe.new([1,2,3]).map{|x|x*2}.__value__
40
+ end
41
+
42
+ should "work if methods takes args and a block" do
43
+ assert_equal nil, Maybe.new(nil).gsub(/x/) {|m| m.upcase}.__value__
44
+ str = Maybe.new('x').gsub(/x/) do |m|
45
+ m.upcase
46
+ end
47
+ assert_equal 'X', str.__value__
48
+ end
49
+
50
+ should "not change the value" do
51
+ x = Maybe.new(1)
52
+ x.to_s
53
+ assert_equal 1, x.__value__
54
+ end
24
55
 
25
- def test_method
26
- assert_equal nil, Maybe.new(nil).to_int.value
27
- assert_equal 2, Maybe.new(2.3).to_int.value
28
56
  end
29
-
30
- def test_method_with_block
31
- assert_equal nil, Maybe.new(nil).map{|x|x*2}.value
32
- assert_equal [2,4,6], Maybe.new([1,2,3]).map{|x|x*2}.value
57
+
58
+ context "when calling object_id" do
59
+
60
+ should "have different object id than wrapped object" do
61
+ wrapped = "hello"
62
+ maybe = Maybe.new(wrapped)
63
+ assert_kind_of Fixnum, maybe.object_id
64
+ assert_not_equal wrapped.object_id, maybe.object_id
65
+ assert_equal wrapped.object_id, maybe.__value__.object_id
66
+ end
67
+
33
68
  end
34
69
 
35
- def test_method__calling_method_doesnt_change_value
36
- x = Maybe.new(1)
37
- x.to_s
38
- assert_equal 1, x.value
70
+ context "#join" do
71
+
72
+ should "not call #pass" do
73
+ Maybe.any_instance.expects(:__pass__).never
74
+ m = Maybe.new(nil)
75
+ m.instance_variable_set(:@value, Maybe.new(1))
76
+ m.join
77
+ m.__join__
78
+ end
79
+
80
+ should "call the wrapped object's #join if defined" do
81
+ wrapped = %w{a b c}
82
+ assert_equal "a b c", Maybe.new(wrapped).join(' ')
83
+ assert_equal "a b c", Maybe.new(Maybe.new(wrapped)).join(' ')
84
+ end
85
+
39
86
  end
40
87
 
41
- def test_method_with_arg_and_block
42
- assert_equal nil, Maybe.new(nil).gsub(/x/) {|m| m.upcase}.value
43
- str = Maybe.new('x').gsub(/x/) do |m|
44
- m.upcase
88
+ context "respond_to?" do
89
+
90
+ should "respond correctly" do
91
+ klass = Class.new do
92
+ def fmap
93
+ end
94
+
95
+ def foo
96
+ end
97
+ end
98
+
99
+ wrapped = klass.new
100
+ maybe = Maybe.new(wrapped)
101
+
102
+ assert_equal false, wrapped.respond_to?(:bar)
103
+ assert_equal true, wrapped.respond_to?(:foo)
104
+ assert_equal true, wrapped.respond_to?(:fmap)
105
+ assert_equal false, wrapped.respond_to?(:join)
106
+ assert_equal false, wrapped.respond_to?(:value)
107
+ assert_equal false, wrapped.respond_to?(:pass)
108
+ assert_equal false, wrapped.respond_to?(:__fmap__)
109
+ assert_equal false, wrapped.respond_to?(:__join__)
110
+ assert_equal false, wrapped.respond_to?(:__value__)
111
+ assert_equal false, wrapped.respond_to?(:__pass__)
112
+
113
+ assert_equal false, maybe.respond_to?(:bar)
114
+ assert_equal true, maybe.respond_to?(:foo)
115
+ assert_equal true, maybe.respond_to?(:fmap)
116
+ assert_equal true, maybe.respond_to?(:join)
117
+ assert_equal true, maybe.respond_to?(:value)
118
+ assert_equal true, maybe.respond_to?(:pass)
119
+ assert_equal true, maybe.respond_to?(:__fmap__)
120
+ assert_equal true, maybe.respond_to?(:__join__)
121
+ assert_equal true, maybe.respond_to?(:__value__)
122
+ assert_equal true, maybe.respond_to?(:__pass__)
45
123
  end
46
- assert_equal 'X', str.value
124
+
47
125
  end
48
126
 
49
- def test_join__doesnt_call_pass
50
- Maybe.any_instance.expects(:pass).never
51
- m = Maybe.new(nil)
52
- m.value = Maybe.new(1)
53
- m.join
127
+ context "#methods" do
128
+
129
+ should "contain methods from wrapped method and wrapper" do
130
+ klass = Class.new do
131
+ def fmap
132
+ end
133
+
134
+ def foo
135
+ end
136
+ end
137
+
138
+ wrapped = klass.new
139
+ maybe = Maybe.new(wrapped)
140
+
141
+ methods = maybe.methods.map{|x| x.to_sym}
142
+
143
+ assert_equal false, methods.include?(:far)
144
+ assert_equal true, methods.include?(:foo)
145
+
146
+ assert_equal true, methods.include?(:fmap)
147
+ assert_equal true, methods.include?(:value)
148
+ assert_equal true, methods.include?(:pass)
149
+ assert_equal true, methods.include?(:join)
150
+
151
+ assert_equal true, methods.include?(:__fmap__)
152
+ assert_equal true, methods.include?(:__value__)
153
+ assert_equal true, methods.include?(:__pass__)
154
+ assert_equal true, methods.include?(:__join__)
155
+ end
156
+
54
157
  end
55
158
 
56
- def test_pass__with_cgi_unescape
57
- # using CGI::unescape because that's the first function I had problems with
58
- # when implementing Maybe
59
- assert_equal nil, Maybe.new(nil).pass {|v|CGI.unescapeHTML(v)}.value
60
- assert_equal '&', Maybe.new('&amp;').pass {|v|CGI.unescapeHTML(v)}.value
159
+ context "#pass" do
160
+
161
+ should "not conflict with wrapped object's #pass method" do
162
+ ball = Object.new
163
+ def ball.pass
164
+ "success"
165
+ end
166
+ assert_equal "success", Maybe.new(ball).pass
167
+ end
168
+
169
+ should "work with CGI.unescape" do
170
+ # using CGI::unescape because that's the first function I had problems with
171
+ # when implementing Maybe
172
+ assert_equal nil, Maybe.new(nil).pass {|v|CGI.unescapeHTML(v)}.value
173
+ assert_equal '&', Maybe.new('&amp;').pass {|v|CGI.unescapeHTML(v)}.value
174
+
175
+ assert_equal nil, Maybe.new(nil).__pass__ {|v|CGI.unescapeHTML(v)}.__value__
176
+ assert_equal '&', Maybe.new('&amp;').__pass__ {|v|CGI.unescapeHTML(v)}.__value__
177
+ end
178
+
61
179
  end
62
180
 
63
- def test_nil
64
- assert_equal true, Maybe.new(nil).nil?
65
- assert_equal false, Maybe.new(1).nil?
181
+ context "#nil?" do
182
+
183
+ should "be true for nil value" do
184
+ assert_equal true, Maybe.new(nil).nil?
185
+ end
186
+
187
+ should "be false for non-nil value" do
188
+ assert_equal false, Maybe.new(1).nil?
189
+ end
190
+
66
191
  end
67
192
 
68
- def test_value__returns_value_with_no_params
69
- assert_equal nil, Maybe.new(nil).value
70
- assert_equal 1, Maybe.new(1).value
193
+ context "#value" do
194
+
195
+ should "return value if wrapped object does not define #value" do
196
+ assert_equal nil, Maybe.new(nil).value
197
+ assert_equal 1, Maybe.new(1).value
198
+ end
199
+
200
+ should "call wrapped object's #value if defined" do
201
+ wrapped = Object.new
202
+ def wrapped.value
203
+ "foo"
204
+ end
205
+ assert_equal "foo", Maybe.new(wrapped).value
206
+ assert_equal "foo", Maybe.new(Maybe.new(wrapped)).value
207
+ end
208
+
209
+ should "call wrapped object's #value if defined (with params and block)" do
210
+ wrapped = Object.new
211
+ def wrapped.value(value)
212
+ value * yield
213
+ end
214
+ assert_equal 4, Maybe.new(wrapped).value(2) { 2 }
215
+ assert_equal 4, Maybe.new(Maybe.new(wrapped)).value(2) { 2 }
216
+ end
217
+
71
218
  end
72
219
 
73
- def test_value__with_default
74
- assert_equal "", Maybe.new(nil).value("")
75
- assert_equal 1, Maybe.new(1).value("1")
76
- assert_equal nil, Maybe.new(nil).value(nil)
77
- assert_equal true, Maybe.new(true).value(nil)
78
- assert_equal false, Maybe.new(nil).value(false)
79
- assert_equal 1, Maybe.new(1).value(false)
220
+ context "#__value__" do
221
+
222
+ should "return value with no params" do
223
+ assert_equal nil, Maybe.new(nil).__value__
224
+ assert_equal 1, Maybe.new(1).__value__
225
+ end
226
+
227
+ context "when default is provided" do
228
+
229
+ should "return default is value is nil" do
230
+ assert_equal "", Maybe.new(nil).__value__("")
231
+ assert_equal nil, Maybe.new(nil).__value__(nil)
232
+ assert_equal false, Maybe.new(nil).__value__(false)
233
+ end
234
+
235
+ should "return value if value is non-nil" do
236
+ assert_equal 1, Maybe.new(1).__value__("1")
237
+ assert_equal true, Maybe.new(true).__value__(nil)
238
+ assert_equal 1, Maybe.new(1).__value__(false)
239
+ end
240
+
241
+ end
242
+
80
243
  end
81
244
 
82
- # the connection between fmap and pass (translated from)
83
- # http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html
84
- # scala version: m map f m flatMap {x => unit(f(x))}
85
- # note that in my code map == fmap && unit==Maybe.new && flatMap==pass
86
- def test_monad_rule_0
87
- f = lambda {|x| x*2}
88
- m = Maybe.new(5)
89
- assert_equal m.fmap(&f), m.pass {|x| Maybe.new(f[x])}
245
+ context "#fmap" do
246
+
247
+ should "call wrapped object's #fmap if defined" do
248
+ wrapped = Object.new
249
+ def wrapped.fmap
250
+ "x"
251
+ end
252
+ assert_equal "x", Maybe.new(wrapped).fmap
253
+ assert_equal "x", Maybe.new(Maybe.new(wrapped)).fmap
254
+ end
255
+
90
256
  end
91
257
 
92
- # monad rules taken from http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/01identity
93
- # and http://james-iry.blogspot.com/2007_10_01_archive.html
258
+ context "when testing monad rules" do
259
+
260
+ # the connection between fmap and pass (translated from)
261
+ # http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html
262
+ # scala version: m map f ≡ m flatMap {x => unit(f(x))}
263
+ # note that in my code map == fmap && unit==Maybe.new && flatMap==pass
264
+ should "satisfy monad rule #0" do
265
+ f = lambda {|x| x*2}
266
+ m = Maybe.new(5)
267
+ assert_equal m.fmap(&f), m.pass {|x| Maybe.new(f[x])}
268
+ assert_equal m.__fmap__(&f), m.__pass__ {|x| Maybe.new(f[x])}
269
+ end
270
+
271
+ # monad rules taken from http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/01identity
272
+ # and http://james-iry.blogspot.com/2007_10_01_archive.html
94
273
 
95
- #1. Calling pass on a newly-wrapped value should have the same effect as giving that value directly to the block.
96
- # (this is actually the second law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
97
- # scala version: unit(x) flatMap f ≡ f(x)
98
- def test_monad_rule_1
99
- f = lambda {|y| Maybe.new(y.to_s)}
100
- x = 1
101
- assert_equal f[x], Maybe.new(x).pass {|y| f[y]}.value
102
- end
274
+ #1. Calling pass on a newly-wrapped value should have the same effect as giving that value directly to the block.
275
+ # (this is actually the second law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
276
+ # scala version: unit(x) flatMap f ≡ f(x)
277
+ should "satisfy monad rule #1" do
278
+ f = lambda {|y| Maybe.new(y.to_s)}
279
+ x = 1
280
+ assert_equal f[x], Maybe.new(x).pass {|y| f[y]}.value
281
+ assert_equal f[x], Maybe.new(x).__pass__ {|y| f[y]}.__value__
282
+ end
103
283
 
104
- #2. pass with a block that simply calls wrap on its value should produce the exact same values, wrapped up again.
105
- # (this is actually the first law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
106
- # scala version: m flatMap unit ≡ m
107
- def test_monad_rule_2
108
- x = Maybe.new(1)
109
- assert_equal x.value, x.pass {|y| Maybe.new(y)}.value
110
- end
284
+ #2. pass with a block that simply calls wrap on its value should produce the exact same values, wrapped up again.
285
+ # (this is actually the first law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
286
+ # scala version: m flatMap unit ≡ m
287
+ should "satisfy monad rule #2" do
288
+ x = Maybe.new(1)
289
+ assert_equal x.value, x.pass {|y| Maybe.new(y)}.value
290
+ assert_equal x.__value__, x.pass {|y| Maybe.new(y)}.__value__
291
+ end
111
292
 
112
- #3. nesting pass blocks should be equivalent to calling them sequentially
113
- def test_monad_rule_3
114
- f = lambda {|x| Maybe.new(x*2)}
115
- g = lambda {|x| Maybe.new(x+1)}
116
- m = Maybe.new(3)
117
- n = Maybe.new(3)
118
- assert_equal m.pass{|x| f[x]}.pass{|x|g[x]}.value, n.pass{|x| f[x].pass{|y|g[y]}}.value
119
- end
293
+ #3. nesting pass blocks should be equivalent to calling them sequentially
294
+ should "satisfy monad rule #3" do
295
+ f = lambda {|x| Maybe.new(x*2)}
296
+ g = lambda {|x| Maybe.new(x+1)}
297
+ m = Maybe.new(3)
298
+ n = Maybe.new(3)
299
+ assert_equal m.pass{|x| f[x]}.pass{|x|g[x]}.value, n.pass{|x| f[x].pass{|y|g[y]}}.value
300
+ assert_equal m.__pass__{|x| f[x]}.__pass__{|x|g[x]}.__value__, n.__pass__{|x| f[x].__pass__{|y|g[y]}}.__value__
301
+ end
120
302
 
303
+ end
121
304
 
122
305
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maybe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ben Brinckerhoff
@@ -9,19 +14,23 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-01-30 00:00:00 -07:00
17
+ date: 2010-12-12 00:00:00 -07:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: mocha
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ~>
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 9
30
+ - 8
23
31
  version: 0.9.8
24
- version:
32
+ type: :development
33
+ version_requirements: *id001
25
34
  description: A library for treating nil and non-nil objects in a similar manner. Technically speaking, Maybe is an implemenation of the maybe monad. The Maybe class wraps any value (nil or non-nil) and lets you treat it as non-nil.
26
35
  email: ben@devver.net
27
36
  executables: []
@@ -30,17 +39,19 @@ extensions: []
30
39
 
31
40
  extra_rdoc_files:
32
41
  - LICENSE
42
+ - README.md
33
43
  - README.rdoc
34
44
  files:
35
45
  - .document
36
46
  - .gitignore
37
47
  - LICENSE
38
- - README.rdoc
48
+ - README.md
39
49
  - Rakefile
40
50
  - VERSION
41
51
  - lib/maybe.rb
42
52
  - test/maybe_test.rb
43
53
  - test/test_helper.rb
54
+ - README.rdoc
44
55
  has_rdoc: true
45
56
  homepage: http://github.com/bhb/maybe
46
57
  licenses: []
@@ -54,18 +65,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
65
  requirements:
55
66
  - - ">="
56
67
  - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
57
70
  version: "0"
58
- version:
59
71
  required_rubygems_version: !ruby/object:Gem::Requirement
60
72
  requirements:
61
73
  - - ">="
62
74
  - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
63
77
  version: "0"
64
- version:
65
78
  requirements: []
66
79
 
67
80
  rubyforge_project:
68
- rubygems_version: 1.3.5
81
+ rubygems_version: 1.3.6
69
82
  signing_key:
70
83
  specification_version: 3
71
84
  summary: A library for treating nil and non-nil objects in a similar manner.