maybe 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -8
- data/VERSION +1 -1
- data/lib/maybe.rb +4 -4
- data/lib/maybe/core_ext.rb +8 -0
- data/test/core_ext_test.rb +24 -0
- data/test/maybe_test.rb +19 -18
- data/test/test_helper.rb +1 -1
- metadata +9 -11
- data/README.rdoc +0 -83
data/README.md
CHANGED
@@ -8,22 +8,28 @@ Synopsis
|
|
8
8
|
|
9
9
|
The Maybe class wraps any value (nil or non-nil) and lets you treat it as non-nil.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
require "maybe"
|
12
|
+
"hello".upcase #=> "HELLO"
|
13
|
+
nil.upcase #=> NoMethodError: undefined method `upcase' for nil:NilClass
|
14
|
+
Maybe.new("hello").upcase.__value__ #=> "HELLO"
|
15
|
+
Maybe.new(nil).upcase.__value__ #=> nil
|
15
16
|
|
16
17
|
You can also use the method `Maybe` for convenience. The following are equivalent:
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
Maybe.new("hello").__value__ #=> "hello"
|
20
|
+
Maybe("hello").__value__ #=> "hello"
|
21
|
+
|
22
|
+
You can also optionally patch `Object` to include a `#maybe` method:
|
23
|
+
|
24
|
+
require "maybe/core_ext"
|
25
|
+
"hello".maybe.upcase #=> "HELLO"
|
20
26
|
|
21
27
|
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
28
|
|
23
29
|
This is especially handy for long chains of method calls, any of which could return nil.
|
24
30
|
|
25
|
-
|
26
|
-
|
31
|
+
# foo, bar, and/or baz could return nil, but this will still work
|
32
|
+
Maybe.new(foo).bar(1).baz(:x)
|
27
33
|
|
28
34
|
Here's a real world example. Instead of writing this:
|
29
35
|
|
@@ -48,6 +54,8 @@ instead of
|
|
48
54
|
Examples
|
49
55
|
--------
|
50
56
|
|
57
|
+
require "maybe"
|
58
|
+
|
51
59
|
Maybe.new("10") #=> A Maybe object, wrapping "10"
|
52
60
|
|
53
61
|
Maybe.new("10").to_i #=> A Maybe object, wrapping 10
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/lib/maybe.rb
CHANGED
@@ -14,7 +14,7 @@ class Maybe
|
|
14
14
|
@value = value
|
15
15
|
__join__
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def respond_to?(method_name)
|
19
19
|
return true if LEGACY_METHODS.include?(method_name.to_s)
|
20
20
|
super || @value.respond_to?(method_name)
|
@@ -62,7 +62,7 @@ class Maybe
|
|
62
62
|
end
|
63
63
|
|
64
64
|
# Only included to provide a complete Monad interface. Not recommended
|
65
|
-
# for general use.
|
65
|
+
# for general use.
|
66
66
|
# (Technically: Given that the value is of type A
|
67
67
|
# takes a function from A->M[B] and returns
|
68
68
|
# M[B] (a monad with a value of type B))
|
@@ -71,7 +71,7 @@ class Maybe
|
|
71
71
|
end
|
72
72
|
|
73
73
|
# Only included to provide a complete Monad interface. Not recommended
|
74
|
-
# for general use.
|
74
|
+
# for general use.
|
75
75
|
# (Technically: Given that the value is of type A
|
76
76
|
# takes a function from A->B and returns
|
77
77
|
# M[B] (a monad with a value of type B))
|
@@ -84,7 +84,7 @@ class Maybe
|
|
84
84
|
end
|
85
85
|
|
86
86
|
# Only included to provide a complete Monad interface. Not recommended
|
87
|
-
# for general use.
|
87
|
+
# for general use.
|
88
88
|
# (Technically: M[M[A]] is equivalent to M[A], that is, monads should be flat
|
89
89
|
# rather than nested)
|
90
90
|
def __join__
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path("test_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class CoreExtTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Object#maybe" do
|
6
|
+
|
7
|
+
should "not include patching by default" do
|
8
|
+
assert_raises NoMethodError do
|
9
|
+
"foo".maybe
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
should "allow calling foo#maybe rather than Maybe.new(foo)" do
|
14
|
+
require 'maybe/core_ext'
|
15
|
+
assert_kind_of Maybe, "foo".maybe
|
16
|
+
end
|
17
|
+
|
18
|
+
teardown do
|
19
|
+
Object.send(:undef_method, :maybe) if Object.new.respond_to?(:maybe)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/test/maybe_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require File.expand_path("test_helper", File.dirname(__FILE__))
|
2
3
|
require 'cgi'
|
3
|
-
require '
|
4
|
+
require 'maybe'
|
4
5
|
|
5
6
|
class MaybeTest < Test::Unit::TestCase
|
6
7
|
|
@@ -14,14 +15,14 @@ class MaybeTest < Test::Unit::TestCase
|
|
14
15
|
Maybe.any_instance.expects(:__pass__).never
|
15
16
|
Maybe.new(Maybe.new(1)).__value__
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
end
|
19
20
|
|
20
21
|
context "when calling methods" do
|
21
22
|
|
22
23
|
should "return correct value for match operator" do
|
23
24
|
assert_equal nil, (Maybe.new(nil)=~/b/).__value__
|
24
|
-
assert_equal 1, (Maybe.new('abc')=~/b/).__value__
|
25
|
+
assert_equal 1, (Maybe.new('abc')=~/b/).__value__
|
25
26
|
end
|
26
27
|
|
27
28
|
should "return correct value for to_s" do
|
@@ -33,7 +34,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
33
34
|
assert_equal nil, Maybe.new(nil).to_int.__value__
|
34
35
|
assert_equal 2, Maybe.new(2.3).to_int.__value__
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
should "work if method call takes a block" do
|
38
39
|
assert_equal nil, Maybe.new(nil).map{|x|x*2}.__value__
|
39
40
|
assert_equal [2,4,6], Maybe.new([1,2,3]).map{|x|x*2}.__value__
|
@@ -41,7 +42,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
41
42
|
|
42
43
|
should "work if methods takes args and a block" do
|
43
44
|
assert_equal nil, Maybe.new(nil).gsub(/x/) {|m| m.upcase}.__value__
|
44
|
-
str = Maybe.new('x').gsub(/x/) do |m|
|
45
|
+
str = Maybe.new('x').gsub(/x/) do |m|
|
45
46
|
m.upcase
|
46
47
|
end
|
47
48
|
assert_equal 'X', str.__value__
|
@@ -56,7 +57,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
56
57
|
end
|
57
58
|
|
58
59
|
context "when calling object_id" do
|
59
|
-
|
60
|
+
|
60
61
|
should "have different object id than wrapped object" do
|
61
62
|
wrapped = "hello"
|
62
63
|
maybe = Maybe.new(wrapped)
|
@@ -68,7 +69,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
68
69
|
end
|
69
70
|
|
70
71
|
context "#join" do
|
71
|
-
|
72
|
+
|
72
73
|
should "not call #pass" do
|
73
74
|
Maybe.any_instance.expects(:__pass__).never
|
74
75
|
m = Maybe.new(nil)
|
@@ -86,7 +87,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
86
87
|
end
|
87
88
|
|
88
89
|
context "respond_to?" do
|
89
|
-
|
90
|
+
|
90
91
|
should "respond correctly" do
|
91
92
|
klass = Class.new do
|
92
93
|
def fmap
|
@@ -95,7 +96,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
95
96
|
def foo
|
96
97
|
end
|
97
98
|
end
|
98
|
-
|
99
|
+
|
99
100
|
wrapped = klass.new
|
100
101
|
maybe = Maybe.new(wrapped)
|
101
102
|
|
@@ -121,7 +122,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
121
122
|
assert_equal true, maybe.respond_to?(:__value__)
|
122
123
|
assert_equal true, maybe.respond_to?(:__pass__)
|
123
124
|
end
|
124
|
-
|
125
|
+
|
125
126
|
end
|
126
127
|
|
127
128
|
context "#methods" do
|
@@ -134,10 +135,10 @@ class MaybeTest < Test::Unit::TestCase
|
|
134
135
|
def foo
|
135
136
|
end
|
136
137
|
end
|
137
|
-
|
138
|
+
|
138
139
|
wrapped = klass.new
|
139
140
|
maybe = Maybe.new(wrapped)
|
140
|
-
|
141
|
+
|
141
142
|
methods = maybe.methods.map{|x| x.to_sym}
|
142
143
|
|
143
144
|
assert_equal false, methods.include?(:far)
|
@@ -167,7 +168,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
167
168
|
end
|
168
169
|
|
169
170
|
should "work with CGI.unescape" do
|
170
|
-
# using CGI::unescape because that's the first function I had problems with
|
171
|
+
# using CGI::unescape because that's the first function I had problems with
|
171
172
|
# when implementing Maybe
|
172
173
|
assert_equal nil, Maybe.new(nil).pass {|v|CGI.unescapeHTML(v)}.value
|
173
174
|
assert_equal '&', Maybe.new('&').pass {|v|CGI.unescapeHTML(v)}.value
|
@@ -175,11 +176,11 @@ class MaybeTest < Test::Unit::TestCase
|
|
175
176
|
assert_equal nil, Maybe.new(nil).__pass__ {|v|CGI.unescapeHTML(v)}.__value__
|
176
177
|
assert_equal '&', Maybe.new('&').__pass__ {|v|CGI.unescapeHTML(v)}.__value__
|
177
178
|
end
|
178
|
-
|
179
|
+
|
179
180
|
end
|
180
181
|
|
181
182
|
context "#nil?" do
|
182
|
-
|
183
|
+
|
183
184
|
should "be true for nil value" do
|
184
185
|
assert_equal true, Maybe.new(nil).nil?
|
185
186
|
end
|
@@ -209,7 +210,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
209
210
|
should "call wrapped object's #value if defined (with params and block)" do
|
210
211
|
wrapped = Object.new
|
211
212
|
def wrapped.value(value)
|
212
|
-
value * yield
|
213
|
+
value * yield
|
213
214
|
end
|
214
215
|
assert_equal 4, Maybe.new(wrapped).value(2) { 2 }
|
215
216
|
assert_equal 4, Maybe.new(Maybe.new(wrapped)).value(2) { 2 }
|
@@ -270,7 +271,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
270
271
|
|
271
272
|
# monad rules taken from http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/01identity
|
272
273
|
# and http://james-iry.blogspot.com/2007_10_01_archive.html
|
273
|
-
|
274
|
+
|
274
275
|
#1. Calling pass on a newly-wrapped value should have the same effect as giving that value directly to the block.
|
275
276
|
# (this is actually the second law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
|
276
277
|
# scala version: unit(x) flatMap f ≡ f(x)
|
@@ -280,7 +281,7 @@ class MaybeTest < Test::Unit::TestCase
|
|
280
281
|
assert_equal f[x], Maybe.new(x).pass {|y| f[y]}.value
|
281
282
|
assert_equal f[x], Maybe.new(x).__pass__ {|y| f[y]}.__value__
|
282
283
|
end
|
283
|
-
|
284
|
+
|
284
285
|
#2. pass with a block that simply calls wrap on its value should produce the exact same values, wrapped up again.
|
285
286
|
# (this is actually the first law at http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html)
|
286
287
|
# scala version: m flatMap unit ≡ m
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 1.0.0
|
9
|
+
version: 1.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ben Brinckerhoff
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2012-07-01 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -40,25 +40,24 @@ extensions: []
|
|
40
40
|
extra_rdoc_files:
|
41
41
|
- LICENSE
|
42
42
|
- README.md
|
43
|
-
- README.rdoc
|
44
43
|
files:
|
45
44
|
- .document
|
46
|
-
- .gitignore
|
47
45
|
- LICENSE
|
48
46
|
- README.md
|
49
47
|
- Rakefile
|
50
48
|
- VERSION
|
51
49
|
- lib/maybe.rb
|
50
|
+
- lib/maybe/core_ext.rb
|
51
|
+
- test/core_ext_test.rb
|
52
52
|
- test/maybe_test.rb
|
53
53
|
- test/test_helper.rb
|
54
|
-
- README.rdoc
|
55
54
|
has_rdoc: true
|
56
55
|
homepage: http://github.com/bhb/maybe
|
57
56
|
licenses: []
|
58
57
|
|
59
58
|
post_install_message:
|
60
|
-
rdoc_options:
|
61
|
-
|
59
|
+
rdoc_options: []
|
60
|
+
|
62
61
|
require_paths:
|
63
62
|
- lib
|
64
63
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -82,6 +81,5 @@ rubygems_version: 1.3.6
|
|
82
81
|
signing_key:
|
83
82
|
specification_version: 3
|
84
83
|
summary: A library for treating nil and non-nil objects in a similar manner.
|
85
|
-
test_files:
|
86
|
-
|
87
|
-
- test/test_helper.rb
|
84
|
+
test_files: []
|
85
|
+
|
data/README.rdoc
DELETED
@@ -1,83 +0,0 @@
|
|
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
|
-
* {MenTaLguY has a great tutorial on Monads in Ruby over at Moonbase}[http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html]
|
66
|
-
* {Oliver Steele explores the problem in depth and looks at a number of different solutions}[http://osteele.com/archives/2007/12/cheap-monads]
|
67
|
-
* {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]
|
68
|
-
* {Weave Jester has another solution, inspired by the Maybe monad}[http://weavejester.com/node/10]
|
69
|
-
|
70
|
-
== Note on Patches/Pull Requests
|
71
|
-
|
72
|
-
* Fork the project.
|
73
|
-
* Make your feature addition or bug fix.
|
74
|
-
* Add tests for it. This is important so I don't break it in a
|
75
|
-
future version unintentionally.
|
76
|
-
* Commit, do not mess with rakefile, version, or history.
|
77
|
-
(if you want to have your own version, that is fine but
|
78
|
-
bump version in a commit by itself I can ignore when I pull)
|
79
|
-
* Send me a pull request. Bonus points for topic branches.
|
80
|
-
|
81
|
-
== Copyright
|
82
|
-
|
83
|
-
Copyright (c) 2010 Ben Brinckerhoff. See LICENSE for details.
|