maybe.rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0161b9efdecf4cc33db1c2e09e5485b026ce291
4
+ data.tar.gz: f38f123c09332b4b3f180e0d654e25a36071b048
5
+ SHA512:
6
+ metadata.gz: b49abd1dd5b67ba7974e1d8896e86afed9fa7c5d228169322cc414bedf074396be964173bd6797ee4909ee6f4549b7b9078c25adf0d0af855ecc68abb2020a7d
7
+ data.tar.gz: 272a780ec77be12dfb3e228de4215312a26ef2ff0cc847de9e016eff55d7ee9e2baa026ac7d6ed7c286799abc5f9d82df1fb158d77ae306e86be1e03ec0539dd
data/.yardopts ADDED
@@ -0,0 +1,13 @@
1
+ ./lib/maybe/**/*.rb ./lib/maybe.rb
2
+ -m markdown
3
+ -M kramdown
4
+ -o yardoc
5
+ -r ./README.md
6
+ --private
7
+ --protected
8
+ --asset ./doc/css/common.css:css/common.css
9
+ --verbose
10
+ -
11
+ ./doc/*.md
12
+ LICENSE
13
+ CONTRIBUTING.md
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015, Olery B.V.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # Maybe.rb
2
+
3
+ > I had a grey horse called maybe, we could not decide what to call her and the
4
+ > US exchange student kept saying "ahh maybe" when we suggested names.
5
+
6
+ A gem providing a very simple Maybe monad for Ruby, minus all the other
7
+ functional programming features often seen in other libraries (such as `bind`).
8
+
9
+ So why would one want to use a Maybe monad? Well, maybe you're writing Ruby, and
10
+ maybe you've got code like this:
11
+
12
+ number = some_array[0]['some_hash_key'] || 0
13
+
14
+ This code however is not guaranteed to always work. For example, if `some_array`
15
+ contains the result of a database query then it might be empty. In such a case
16
+ you'll get the dreaded nil error:
17
+
18
+ NoMethodError: undefined method `[]' for nil:NilClass
19
+
20
+ There are a few ways you can work around this, for example using an `if`
21
+ statement:
22
+
23
+ if some_array[0]
24
+ number = some_array[0]['some_hash_key']
25
+ else
26
+ number = 0
27
+ end
28
+
29
+ This can be shortened a bit using the ternary operator:
30
+
31
+ number = some_array[0] ? some_array[0]['some_hash_key'] : 0
32
+
33
+ Alternatively you can take the Ostrich approach and just bury your head in the
34
+ sand:
35
+
36
+ number = some_array[0]['some_hash_key'] rescue 0
37
+
38
+ Ostrich based programming however has its problems. Because in the above case we
39
+ rescue _all_ errors we could potentially rescue too much and introduce bugs as a
40
+ result. For example, if `some_array` were a method returning data from an API,
41
+ and said API would raise an authentication error we'd never know.
42
+
43
+ Another problem of the statement based solution is code duplication. Even when
44
+ using the ternary operator we have to repeat some part of the code we're trying
45
+ to access (`some_array[0]` in the above example).
46
+
47
+ Using this particular Gem we can write the above code as following:
48
+
49
+ number = Maybe.new(some_array)[0]['some_hash_key'].or(0)
50
+
51
+ This particular syntax can be made a bit shorter by loading the `maybe/pollute`
52
+ file which adds `Object#maybe`, allowing you to write the following instead:
53
+
54
+ number = some_array.maybe[0]['some_hash_key'].or(0)
55
+
56
+ This is disabled by default and the `Maybe` class itself doesn't rely on it and
57
+ because it's not too pleasant for a library to implicitly patch built-in Ruby
58
+ classes.
59
+
60
+ ## Usage
61
+
62
+ Load the Gem:
63
+
64
+ require 'maybe'
65
+
66
+ Optionally pollute `Object` so you can just call `maybe` on any object:
67
+
68
+ require 'maybe/polluate'
69
+
70
+ Wrap a value using the `Maybe` class directly:
71
+
72
+ numbers = [10]
73
+
74
+ Maybe.new(numbers)[1].or(20) # => 20
75
+
76
+ Using `Object#maybe`:
77
+
78
+ numbers = [10]
79
+
80
+ numbers.maybe[1].or(20) # => 20
81
+
82
+ The Maybe class also allows you to use a block instead of the `[]` method (which
83
+ is just an alias for `Maybe#maybe`):
84
+
85
+ numbers = [10, 20, 30]
86
+
87
+ numbers.maybe(0) { |value| value * 10 }.or(1) # => 100
88
+ numbers.maybe(3) { |value| value * 10 }.or(1) # => 1
89
+
90
+ Calling `Maybe#maybe` also works without an argument, in which case the
91
+ currently wrapped value is yielded to the block (if there is any value to
92
+ yield):
93
+
94
+ numbers[0].maybe { |value| value.to_s }.or('1') # => '10'
95
+
96
+ For more examples, see the documentation (and source code) of
97
+ [lib/maybe/maybe.rb](lib/maybe/maybe.rb).
98
+
99
+ ## What is this magic?
100
+
101
+ The way this Monad works is that any non `nil` value is yielded to a supplied
102
+ block or returned as a new Maybe instance. The moment you call `Maybe#or` this
103
+ method will verify the currently wrapped value and decide what to return.
104
+
105
+ If the wrapped value is not nil it's simply returned. If the value is nil and a
106
+ block is given then said block is yielded. If the value is nil and _no_ block is
107
+ given the method will simply return its argument (`nil` by default).
108
+
109
+ Some examples (using `Object#maybe` for better readability):
110
+
111
+ 10.maybe.or(20) # => 10
112
+ 10.maybe.or { 20 } # => 10
113
+ 10.maybe.or # => 10
114
+
115
+ nil.maybe.or(20) # => 20
116
+ nil.maybe.or { 20 } # => 20
117
+ nil.maybe.or # => nil, since the default return value is nil
118
+
119
+ ## Requirements
120
+
121
+ * Ruby
122
+
123
+ ## License
124
+
125
+ All source code in this repository is licensed under the MIT license unless
126
+ specified otherwise. A copy of this license can be found in the file "LICENSE"
127
+ in the root directory of this repository.
data/doc/DCO.md ADDED
@@ -0,0 +1,25 @@
1
+ # Developer's Certificate of Origin 1.0
2
+
3
+ By making a contribution to this project, I certify that:
4
+
5
+ 1. The contribution was created in whole or in part by me and I
6
+ have the right to submit it under the open source license
7
+ indicated in the file LICENSE; or
8
+
9
+ 2. The contribution is based upon previous work that, to the best
10
+ of my knowledge, is covered under an appropriate open source
11
+ license and I have the right under that license to submit that
12
+ work with modifications, whether created in whole or in part
13
+ by me, under the same open source license (unless I am
14
+ permitted to submit under a different license), as indicated
15
+ in the file LICENSE; or
16
+
17
+ 3. The contribution was provided directly to me by some other
18
+ person who certified (1), (2) or (3) and I have not modified
19
+ it.
20
+
21
+ 4. I understand and agree that this project and the contribution
22
+ are public and that a record of the contribution (including all
23
+ personal information I submit with it, including my sign-off) is
24
+ maintained indefinitely and may be redistributed consistent with
25
+ this project or the open source license(s) involved.
@@ -0,0 +1,77 @@
1
+ body
2
+ {
3
+ font-size: 14px;
4
+ line-height: 1.6;
5
+ margin: 0 auto;
6
+ max-width: 960px;
7
+ }
8
+
9
+ p code, dd code, li code
10
+ {
11
+ background: #f9f2f4;
12
+ color: #c7254e;
13
+ border-radius: 4px;
14
+ padding: 2px 4px;
15
+ }
16
+
17
+ pre.code
18
+ {
19
+ font-size: 13px;
20
+ line-height: 1.4;
21
+ overflow: auto;
22
+ }
23
+
24
+ blockquote
25
+ {
26
+ border-left: 5px solid #eee;
27
+ margin: 0px;
28
+ padding-left: 15px;
29
+ }
30
+
31
+ /**
32
+ * YARD uses generic table styles, using a special class means those tables
33
+ * don't get messed up.
34
+ */
35
+ .table
36
+ {
37
+ border: 1px solid #ccc;
38
+ border-right: none;
39
+ border-collapse: separate;
40
+ border-spacing: 0;
41
+ text-align: left;
42
+ }
43
+
44
+ .table.full
45
+ {
46
+ width: 100%;
47
+ }
48
+
49
+ .table .field_name
50
+ {
51
+ min-width: 160px;
52
+ }
53
+
54
+ .table thead tr th.no_sort:first-child
55
+ {
56
+ width: 25px;
57
+ }
58
+
59
+ .table thead tr th, .table tbody tr td
60
+ {
61
+ border-bottom: 1px solid #ccc;
62
+ border-right: 1px solid #ccc;
63
+ min-width: 20px;
64
+ padding: 8px 5px;
65
+ text-align: left;
66
+ vertical-align: top;
67
+ }
68
+
69
+ .table tbody tr:last-child td
70
+ {
71
+ border-bottom: none;
72
+ }
73
+
74
+ .table tr:nth-child(odd) td
75
+ {
76
+ background: #f9f9f9;
77
+ }
@@ -0,0 +1,155 @@
1
+ ##
2
+ # The Maybe class is a very simple implemention of the maybe monad/option type
3
+ # often found in functional programming languages. To explain the use of this
4
+ # class, lets take at the following code:
5
+ #
6
+ # pairs = [{:a => 10}]
7
+ #
8
+ # number = pairs[0][:a]
9
+ #
10
+ # While this code is very simple, there are already a few problems that can
11
+ # arise:
12
+ #
13
+ # 1. If the pairs Array is empty we'll get an "undefined method [] for nil"
14
+ # error as pairs[0] in this case returns nil.
15
+ # 2. If the :a key is not set we'll again end up with a nil value, which might
16
+ # break code later on.
17
+ #
18
+ # So lets add some code to take care of this, ensuring we _always_ have a
19
+ # number:
20
+ #
21
+ # pairs = [{:a => 10}]
22
+ #
23
+ # number = pairs[0] ? pairs[0][:a] || 0 : 0
24
+ #
25
+ # Alternatively:
26
+ #
27
+ # pairs = [{:a => 10}]
28
+ #
29
+ # number = (pairs[0] ? pairs[0][:a] : nil) || 0
30
+ #
31
+ # Both cases are quite messy. Using the Maybe class we can write this as
32
+ # following instead:
33
+ #
34
+ # pairs = [{:a => 10}]
35
+ #
36
+ # numbers = Maybe.new(pairs[0]).maybe(:a).or(0)
37
+ #
38
+ # If we use the patch for Object (adding `Object#maybe`) we can write this as
39
+ # following:
40
+ #
41
+ # pairs = [{:a => 10}]
42
+ #
43
+ # numbers = pairs[0].maybe(:a).or(0)
44
+ #
45
+ # Or even better:
46
+ #
47
+ # pairs = [{:a => 10}]
48
+ #
49
+ # numbers = pairs.maybe[0][:a].or(0)
50
+ #
51
+ # Boom, we no longer need to rely on ternaries or the `||` operator to ensure we
52
+ # always have a default value.
53
+ #
54
+ class Maybe
55
+ ##
56
+ # @param [Mixed] wrapped
57
+ #
58
+ def initialize(wrapped)
59
+ @wrapped = wrapped
60
+ end
61
+
62
+ ##
63
+ # @return [Mixed]
64
+ #
65
+ def unwrap
66
+ return @wrapped
67
+ end
68
+
69
+ ##
70
+ # Retrieves a value from the wrapped object. This method can be used in two
71
+ # ways:
72
+ #
73
+ # 1. Using a member (e.g. Array index or Hash key) in case the underlying
74
+ # object defines a "[]" method.
75
+ #
76
+ # 2. Using a block which takes the wrapped value as its argument.
77
+ #
78
+ # For example, to get index 0 from an Array and return it as a Maybe:
79
+ #
80
+ # Maybe.new([10, 20, 30]).maybe(0) # => #<Maybe:0x0...>
81
+ #
82
+ # To get a Hash key:
83
+ #
84
+ # Maybe.new({:a => 10}).maybe(:a) # => #<Maybe:0x00...>
85
+ #
86
+ # Using a block:
87
+ #
88
+ # Maybe.new([10, 20, 30]).maybe { |array| array[0] }
89
+ #
90
+ # The block is only evaluated if the current Maybe wraps a non-nil value. The
91
+ # return value of the block is wrapped in a new Maybe instance.
92
+ #
93
+ # This method is aliased as `[]` allowing usage such as the following:
94
+ #
95
+ # maybe = Maybe.new([{'foo' => 10}])
96
+ #
97
+ # maybe[0]['foo'].or(20)
98
+ #
99
+ # @param [Array] args
100
+ #
101
+ # @yieldparam [Mixed] wrapped The wrapped value
102
+ #
103
+ # @return [Maybe]
104
+ #
105
+ def maybe(*args)
106
+ if !args.empty?
107
+ value = brackets? ? @wrapped[*args] : nil
108
+ elsif @wrapped
109
+ value = yield @wrapped
110
+ end
111
+
112
+ if value.nil? and @wrapped.nil?
113
+ # No point in allocating a new Maybe for this.
114
+ return self
115
+ else
116
+ return Maybe.new(value)
117
+ end
118
+ end
119
+
120
+ alias_method :[], :maybe
121
+
122
+ ##
123
+ # Returns the wrapped value or returns a default value, specified as either an
124
+ # argument to this method or in the provided block.
125
+ #
126
+ # A simple example:
127
+ #
128
+ # Maybe.new([10, 20]).maybe(0).or(9000)
129
+ #
130
+ # And using a block:
131
+ #
132
+ # Maybe.new([10, 20]).maybe(0).or { 9000 }
133
+ #
134
+ # @param [Mixed] default
135
+ # @return [Mixed]
136
+ #
137
+ def or(default = nil)
138
+ return @wrapped unless @wrapped.nil?
139
+
140
+ if block_given?
141
+ return yield
142
+ else
143
+ return default
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ ##
150
+ # @return [TrueClass|FalseClass]
151
+ #
152
+ def brackets?
153
+ return @wrapped.respond_to?(:[])
154
+ end
155
+ end # Maybe
@@ -0,0 +1,23 @@
1
+ class Object
2
+ ##
3
+ # Wraps the current object in a Maybe instance, optionally calling {Maybe#get}
4
+ # if an argument or block is given.
5
+ #
6
+ # @example
7
+ # [10, 20, 30].maybe(0).or(50) # => 10
8
+ # [10, 20, 30].maybe { |arr| arr[3] }.or(50) # => 50
9
+ #
10
+ # @see [Maybe#get]
11
+ #
12
+ def maybe(*args)
13
+ retval = Maybe.new(self)
14
+
15
+ if !args.empty?
16
+ retval = retval.maybe(*args)
17
+ elsif block_given?
18
+ retval = retval.maybe(&Proc.new)
19
+ end
20
+
21
+ return retval
22
+ end
23
+ end # Object
@@ -0,0 +1,3 @@
1
+ class Maybe
2
+ VERSION = '1.0.0'
3
+ end # Maybe
data/lib/maybe.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative 'maybe/version'
2
+ require_relative 'maybe/maybe'
data/maybe.rb.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ require File.expand_path('../lib/maybe/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'maybe.rb'
5
+ gem.version = Maybe::VERSION
6
+ gem.authors = ['Olery B.V.']
7
+ gem.email = 'development@olery.com'
8
+ gem.summary = 'A basic maybe monad in Ruby'
9
+ gem.homepage = 'https://github.com/olery/maybe/'
10
+ gem.description = gem.summary
11
+ gem.license = 'MIT'
12
+
13
+ gem.files = Dir.glob([
14
+ 'doc/**/*',
15
+ 'lib/**/*.rb',
16
+ 'README.md',
17
+ 'LICENSE',
18
+ 'maybe.rb.gemspec',
19
+ '.yardopts'
20
+ ]).select { |file| File.file?(file) }
21
+
22
+ gem.has_rdoc = 'yard'
23
+ gem.required_ruby_version = '>= 1.9.3'
24
+
25
+ gem.add_development_dependency 'yard'
26
+ gem.add_development_dependency 'rake'
27
+ gem.add_development_dependency 'rspec'
28
+ gem.add_development_dependency 'kramdown'
29
+ gem.add_development_dependency 'simplecov'
30
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maybe.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Olery B.V.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: yard
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: kramdown
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A basic maybe monad in Ruby
84
+ email: development@olery.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".yardopts"
90
+ - LICENSE
91
+ - README.md
92
+ - doc/DCO.md
93
+ - doc/css/common.css
94
+ - lib/maybe.rb
95
+ - lib/maybe/maybe.rb
96
+ - lib/maybe/pollute.rb
97
+ - lib/maybe/version.rb
98
+ - maybe.rb.gemspec
99
+ homepage: https://github.com/olery/maybe/
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 1.9.3
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.4.5
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: A basic maybe monad in Ruby
123
+ test_files: []
124
+ has_rdoc: yard