ryansch-andand 1.3.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.
@@ -0,0 +1,25 @@
1
+ == 1.3.2
2
+
3
+ * 1 minor enhancement
4
+ * better docs
5
+ * 2 major enhancement:
6
+ * Fix 1.9 warning (patch by George MacKerron)
7
+ * 3 major enhancement:
8
+ * Remove deprecated gemspec information (patch by Anthony Panozzo)
9
+
10
+ == 1.3.1 2008-07-12
11
+
12
+ * 1 major enhancement:
13
+ * Initial release
14
+ * 2 tiny enhancement:
15
+ * fixed specifications
16
+ * 3 minor enhancement
17
+ * tap is now a synonym for me
18
+ * 4 minor enhancement
19
+ * Object#dont
20
+ * 5 bug fix
21
+ * Plays well with other patch-artists
22
+ * 6 minor enhancement
23
+ * uses existing BlankSlate class if already defined
24
+ * 7 tiny enhancement
25
+ * use BasicObject as well
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2008 Reginald Braithwaite
2
+ http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Some code adapted from Jim Weirich's post:
26
+ http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
@@ -0,0 +1,162 @@
1
+ h1. Object#andand
2
+
3
+ h2. Breaking News
4
+
5
+ @Object#andand@'s functionality is also available as part of the "RewriteRails":http://github.com/raganwald/rewrite_rails plug-in. If you are using Ruby on Rails, please consider using RewriteRails instead of the andand gem.
6
+
7
+ h2. What
8
+
9
+ @Object#andand@ lets us write:
10
+
11
+ <pre syntax="ruby">
12
+ @phone = Location.find(:first, ...elided... ).andand.phone
13
+ </pre>And get a _guarded method invocation_ or _safe navigation method_. This snippet performs a @.find@ on the Location class, then sends @.phone@ to the result _if the result is not nil_. If the result is nil, then the expression returns nil without throwing a NoMethodError.
14
+
15
+ As Dejan Simic "put it":http://rors.org/2008/3/18/andand:
16
+
17
+ Why would you want to write this:
18
+
19
+ <pre syntax="ruby">
20
+ entry.at('description') && entry.at('description').inner_text
21
+ </pre>when you can write this:
22
+
23
+ <pre syntax="ruby">
24
+ entry.at('description').andand.inner_text
25
+ </pre>Why indeed! As a bonus, install andand and you will also receive an enhanced Object#tap method, _at no extra charge_!
26
+
27
+ h2. Installing
28
+
29
+ Update to RubyGems 1.2.0 before proceeding!!
30
+
31
+ <pre syntax="ruby">sudo gem sources -a http://gems.github.com (you only have to do this once)
32
+ sudo gem install raganwald-andand</pre>
33
+
34
+ Or:
35
+
36
+ <pre syntax="ruby">git clone git://github.com/raganwald/andand.git
37
+ cd andand
38
+ rake gem
39
+ rake install_gem</pre>
40
+
41
+ h2. The basics
42
+
43
+ h3. Object#andand
44
+
45
+ Ruby programmers are familiar with the two _guarded assignment_ operators @&&=@ and @||=@. The typical use for them is when you have a variable that might be nil. For example:
46
+
47
+ <pre syntax="ruby">
48
+ first_name &&= @first_name.trim
49
+ @phone ||= '612-777-9311'
50
+ </pre>You are trimming the first name provided it isn’t nil, and you are assigning ‘612-777-9311’ to the phone if it _is_ nil (or false, but that isn’t important right now). The other day we were discussing the guards and we agreed that we wished there was a _guarded method invocation_ operator. Here’s an example of when you would use it:
51
+
52
+ <pre syntax="ruby">
53
+ @phone = Location.find(:first, ...elided... )&&.phone
54
+ </pre>Meaning, search the location table for the first record matching some criteria, and if you find a location, get its phone. If you don’t, get nil. (Groovy provides this exact functionality, although Groovy uses @?.@ instead of @&&.@) However, @&&.@ won’t work because @&&.@ is not a real Ruby operator.
55
+
56
+ Object#andand let&rsquo;s us write:
57
+
58
+ <pre syntax="ruby">
59
+ @phone = Location.find(:first, ...elided... ).andand.phone
60
+ </pre>And get the same effect as:
61
+
62
+ <pre syntax="ruby">
63
+ @phone = ->(loc){ loc && loc.phone }.call(Location.find(:first, ...elided... ))
64
+ </pre>Note that because you accept any method using Ruby’s method invocation syntax, you can accept methods with parameters and/or blocks:
65
+
66
+ <pre syntax="ruby">
67
+ list_of_lists.detect { ...elided... }.andand.inject(42) { ...elided ... }
68
+ </pre>Object#andand emphasizes syntactic regularity: the goal was to make an @&&.@ operation that worked like @&&=@. @&&=@ looks just like normal assignment, you can use any expression on the RHS, only the semantics are different. The andand method also works just like a normal method invocation, only the semantics are modified.
69
+
70
+ h3. Block-Structured andand
71
+
72
+ You can use @andand@ with a block instead of a method:
73
+
74
+ <pre syntax="ruby">
75
+ @phone = Location.find(:first, ...elided... ).andand { |location|
76
+ YellowPages.reverse_lookup(location).phone
77
+ }
78
+ </pre>If the receiver is nil, the block is not executed and @andand@ returns @nil@. If the receiver is not nil, it is passed as a parameter to the block and @andand@ returns the value of the block. This makes it possible to perform conditional evaluation and also to make the scope of the variable really obvious.
79
+
80
+ h3. Scope
81
+
82
+ @Object#andand@ only works for one method call. For example, @fu.andand.bar.blitz@ is going to call @nil.blitz@ if @fu@ is @nil@. That's because @fu.andand.bar@ is going to evaluate to @nil@, and then we will call the method @blitz@ on it. In most cases, you want to use @fu.andand.bar.andand.blitz@. If that seems awkward, you might want to reconsider violating the Law of Demeter in an environment where you can't be sure if your receiver is @nil@ or not.
83
+
84
+ Another example of this (pointed out by Jan Zimmek):
85
+
86
+ <pre syntax="ruby">
87
+ x = nil
88
+ x.andand.length > 3
89
+ </pre>This results in a @NoMethodError@. Again, @x@ is @nil@, therefore @x.andand.length@ is @nil@, therefore we end up with @nil > 3@ which results in a @NoMethodError@. This can be fixed with @x.andand.length.andand > 3@ as above, or perhaps:
90
+
91
+ <pre syntax="ruby">
92
+ x = nil
93
+ x.andand { |value| value.length > 3 }
94
+ </pre>
95
+
96
+ h3. Enhanced Object#tap
97
+
98
+ Ruby 1.9 introduces "Object#tap":http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions. This library implements Object#tap for Ruby 1.8 *and* enhances it. As in Ruby 1.9, you can call @.tap@ with a block:
99
+
100
+ <pre syntax="ruby">
101
+ blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
102
+ </pre> But like its sibling @.andand@, you can now call @.tap@ with a method as well:
103
+
104
+ <pre syntax="ruby">
105
+ [1, 2, 3, 4, 5].tap.pop.map { |n| n * 2 }
106
+ => [2, 4, 6, 8]
107
+ </pre>
108
+
109
+ h3. Doctor, it hurts when I do that
110
+
111
+ _So don't do that!_
112
+
113
+ The popular use case for Object#tap is poor man's debugging:
114
+
115
+ <pre syntax="ruby">
116
+ blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
117
+ </pre>Perhaps you want to remove the tap, you can delete it:
118
+
119
+ <pre syntax="ruby">
120
+ blah.sort.grep( /foo/ ).map { |x| x.blah }
121
+ </pre>Or, you can change it to @.dont@:
122
+
123
+ <pre syntax="ruby">
124
+ blah.sort.grep( /foo/ ).dont { |xs| p xs }.map { |x| x.blah }
125
+ </pre>Like @.andand@ and @.tap@, @.dont@ works with arbitrary methods, not just blocks:
126
+
127
+ <pre syntax="ruby">
128
+ (1..10).to_a.reverse!
129
+ => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
130
+ (1..10).to_a.dont.reverse!
131
+ => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
132
+ </pre>
133
+
134
+ h2. A little more background
135
+
136
+ "Object#andand & Object#me in Ruby":http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html explains the original motivations, as well as providing links to similar implementations you may want to consider. A few people have pointed out that Object#andand is similar to Haskell's Maybe monad. "The Maybe Monad in Ruby":http://blog.pretheory.com/arch/2008/02/the_maybe_monad_in_ruby.php is a good introduction for Ruby programmers.
137
+
138
+ h2. That's cool, but&hellip;
139
+
140
+ No problem, I get that andand isn't exactly what you need. Have a look at the "Invocation Construction Kit":http://ick.rubyforge.org or "Ick." The Ick gem _generalizes_ #andand and #tap: Ick provides four useful ways to block-structure your code, the methods #let, #returning, #inside, and #my. Ick also includes four quasi-monadic invocations, #maybe, #please, #tee, and #fork.
141
+
142
+ "Ick":http://ick.rubyforge.org provides abstractions for building your own invocations, so you can branch out and build some of your own abstractions with Ick's building blocks.
143
+
144
+ h2. How to submit patches
145
+
146
+ Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/.
147
+
148
+ The public clone url is @git://github.com/raganwald/andand.git@. Fork you very much.
149
+
150
+ h2. License
151
+
152
+ This code is free to use under the terms of the "MIT license":http://en.wikipedia.org/wiki/MIT_License.
153
+
154
+ h2. Shout Out
155
+
156
+ "Mobile Commons":http://mcommons.com/. Huge.
157
+
158
+ Also interesting: "Wicked":http://github.com/wideopenspaces/wicked
159
+
160
+ h2. Contact
161
+
162
+ Comments are welcome. Send an email to "Reginald Braithwaite":mailto:raganwald@gmail.com.
@@ -0,0 +1,148 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module AndAnd
4
+
5
+ # This module is included in Object, so each of these methods are added
6
+ # to Object when you require 'andand'. Each method is an *adverb*: they are
7
+ # intended to be enchained with another method, such as receiver.adverb.method
8
+ #
9
+ # The purpose of an adverb is to modify what the primary method returns.
10
+ #
11
+ # Adverbs also take blocks or procs, passing the receiver as an argument to the
12
+ # block or proc. They retain the same semantics with a block or proc as they
13
+ # do with a method. This behaviour weakly resembles a monad.
14
+ module ObjectGoodies
15
+
16
+ # Returns nil if its receiver is nil, regardless of whether nil actually handles the
17
+ # actual method ot what it might return.
18
+ #
19
+ # 'foo'.andand.size => 3
20
+ # nil.andand.size => nil
21
+ # 'foo'.andand { |s| s << 'bar' } => 'foobar'
22
+ # nil.andand { |s| s << 'bar' } => nil
23
+ def andand (p = nil)
24
+ if self
25
+ if block_given?
26
+ yield(self)
27
+ elsif p
28
+ p.to_proc.call(self)
29
+ else
30
+ self
31
+ end
32
+ else
33
+ if block_given? or p
34
+ self
35
+ else
36
+ MockReturningMe.new(self)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Invokes the method and returns the receiver if nothing is raised. Therefore,
42
+ # the purpose of calling the method is strictly for side effects. In the block
43
+ # form, it resembles #tap from Ruby 1.9, and is useful for debugging. It also
44
+ # resembles #returning from Rails, with slightly different syntax.
45
+ #
46
+ # Object.new.me do |o|
47
+ # def o.foo
48
+ # 'foo'
49
+ # end
50
+ # end
51
+ # => your new object
52
+ #
53
+ # In the method form, it is handy for chaining methods that don't ordinarily
54
+ # return the receiver:
55
+ #
56
+ # [1, 2, 3, 4, 5].me.pop.reverse
57
+ # => [4, 3, 2, 1]
58
+ def me (p = nil)
59
+ if block_given?
60
+ yield(self)
61
+ self
62
+ elsif p
63
+ p.to_proc.call(self)
64
+ self
65
+ else
66
+ ProxyReturningMe.new(self)
67
+ end
68
+ end
69
+
70
+ unless Object.instance_methods.include?('tap')
71
+ alias :tap :me
72
+ end
73
+
74
+ # Does not invoke the method or block and returns the receiver.
75
+ # Useful for comemnting stuff out, especially if you are using #me for
76
+ # debugging purposes: change the .me to .dont and the semantics of your
77
+ # program are unchanged.
78
+ #
79
+ # [1, 2, 3, 4, 5].me { |x| p x }
80
+ # => prints and returns the array
81
+ # [1, 2, 3, 4, 5].dont { |x| p x }
82
+ # => returns the array without printing it
83
+ def dont (p = nil)
84
+ if block_given?
85
+ self
86
+ elsif p
87
+ self
88
+ else
89
+ MockReturningMe.new(self)
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ class Object
98
+ include AndAnd::ObjectGoodies
99
+ end
100
+
101
+ unless Module.constants.map { |c| c.to_s }.include?('BlankSlate')
102
+ if Module.constants.map { |c| c.to_s }.include?('BasicObject')
103
+ module AndAnd
104
+ class BlankSlate < BasicObject
105
+ end
106
+ end
107
+ else
108
+ module AndAnd
109
+ class BlankSlate
110
+ def self.wipe
111
+ instance_methods.reject { |m| m =~ /^__/ }.each { |m| undef_method m }
112
+ end
113
+ def initialize
114
+ BlankSlate.wipe
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ module AndAnd
122
+
123
+ # A proxy that returns its target without invoking the method you
124
+ # invoke. Useful for nil.andand and #dont
125
+ class MockReturningMe < BlankSlate
126
+ def initialize(me)
127
+ super()
128
+ @me = me
129
+ end
130
+ def method_missing(*args)
131
+ @me
132
+ end
133
+ end
134
+
135
+ # A proxy that returns its target after invoking the method you
136
+ # invoke. Useful for #me
137
+ class ProxyReturningMe < BlankSlate
138
+ def initialize(me)
139
+ super()
140
+ @me = me
141
+ end
142
+ def method_missing(sym, *args, &block)
143
+ @me.__send__(sym, *args, &block)
144
+ @me
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,9 @@
1
+ module Andand #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 3
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,133 @@
1
+ =begin
2
+
3
+ Copyright (c) 2008 Reginald Braithwaite
4
+ http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the "Software"), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
26
+
27
+ =end
28
+
29
+ require File.dirname(__FILE__) + '/test_helper.rb'
30
+
31
+ require 'andand'
32
+
33
+ class Symbol
34
+ def to_proc
35
+ Proc.new { |*args| args.shift.__send__(self, *args) }
36
+ end
37
+ end
38
+
39
+ describe AndAnd, "Basic Behaviour" do
40
+ it "should conditionally yield to a block" do
41
+ 5.andand { |n| n*n }.should == 25
42
+ 5.andand(&:succ).should == 6
43
+ nil.andand { |n| n*n }.should == nil
44
+ false.andand { |n| n*n }.should == false
45
+ end
46
+ it "should conditionally call a proc-able parameter" do
47
+ 5.andand(:succ).should == 6
48
+ nil.andand(:succ).should == nil
49
+ false.andand(:succ).should == false
50
+ end
51
+ it "should conditionally proxy methods" do
52
+ 5.andand.succ.should == 6
53
+ nil.andand.succ.should == nil
54
+ false.andand.succ.should == false
55
+ (1..10).andand.inject(&:+).should == 55
56
+ nil.andand.inject(&:+).should == nil
57
+ false.andand.inject(&:+).should == false
58
+ 'HelloWeblogReaders'.andand[7,4].should == 'blog'
59
+ nil.andand[7,4].should == nil
60
+ false.andand[7,4].should == false
61
+ 'HelloWeblogReaders'.andand.tr('Wb','Bd').should == 'HelloBedlogReaders'
62
+ nil.andand.tr('Wb','Bd').should == nil
63
+ false.andand.tr('Wb','Bd').should == false
64
+ end
65
+ it "should handle infix operators" do
66
+ (5.andand * 2).should == 10
67
+ (nil.andand * 2).should == nil
68
+ (false.andand * 2).should == false
69
+ end
70
+ end
71
+
72
+ describe AndAnd, "Me" do
73
+ it "should unconditionally yield to a block" do
74
+ 5.me { |n| n*n }.should == 5
75
+ 5.me(&:succ).should == 5
76
+ nil.me { |n| n || true }.should be_nil
77
+ false.me { |n| n || true }.should == false
78
+ end
79
+ it "should unconditionally call a proc-able parameter" do
80
+ 5.me(:to_s).should == 5
81
+ nil.me(:to_s).should == nil
82
+ false.me(:to_s).should == false
83
+ end
84
+ it "should proxy methods" do
85
+ 5.me.to_s.should == 5
86
+ nil.me.to_s.should == nil
87
+ false.me.to_s.should == false
88
+ (1..10).me.inject(&:+).should == (1..10)
89
+ 'HelloWeblogReaders'.me[7,4].should == 'HelloWeblogReaders'
90
+ 'HelloWeblogReaders'.me.tr('Wb','Bd').should == 'HelloWeblogReaders'
91
+ end
92
+ it "should handle infix operators" do
93
+ (5.me * 2).should == 5
94
+ end
95
+ end
96
+
97
+ describe AndAnd, "Mixing Me with AndAnd" do
98
+ it "should compose me.andand" do
99
+ frobbish = :blitz
100
+ :foo.me.andand do
101
+ frobbish = :bar
102
+ end.should == :foo
103
+ frobbish.should == :bar
104
+ nil.me.andand do
105
+ frobbish = :barbarella
106
+ end.should be_nil
107
+ frobbish.should == :bar
108
+ false.me.andand do
109
+ frobbish = :hope
110
+ end.should == false
111
+ frobbish.should == :bar
112
+ end
113
+ end
114
+
115
+ class Foo
116
+ def frobbish
117
+ 'fnord'
118
+ end
119
+ end
120
+
121
+ describe AndAnd, "exception handling" do
122
+ it "should not swallow NoMethodErrors" do
123
+ foo = Foo.new
124
+ lambda do
125
+ foo.andand.frobbish
126
+ nil.andand.frobbish
127
+ nil.andand.hsibborf
128
+ end.should_not raise_error
129
+ lambda do
130
+ foo.andand.hsibborf
131
+ end.should raise_error
132
+ end
133
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/andand'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ryansch-andand
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 2
10
+ version: 1.3.2
11
+ platform: ruby
12
+ authors:
13
+ - Reg Braithwaite
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2008-11-07 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: " Maybe Monad in idiomatic Ruby."
23
+ email: reg@braythwayt.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - History.txt
30
+ - README.textile
31
+ files:
32
+ - History.txt
33
+ - License.txt
34
+ - README.textile
35
+ - lib/andand.rb
36
+ - lib/andand/version.rb
37
+ - test/andand_spec.rb
38
+ - test/test_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/ryansch/andand
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --main
46
+ - README.textile
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.6.2
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: The Maybe Monad in idiomatic Ruby
74
+ test_files:
75
+ - test/andand_spec.rb
76
+ - test/test_helper.rb