maybe.rb 1.0.0

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.
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