option 0.0.1
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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +62 -0
- data/Rakefile +9 -0
- data/lib/option.rb +115 -0
- data/lib/option/version.rb +3 -0
- data/option.gemspec +21 -0
- data/spec/option_spec.rb +157 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Rob Ares
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following 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 OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Option
|
2
|
+
|
3
|
+
A Ruby port os Scala's Option monad. Tries to be faithful
|
4
|
+
but also pragmatic in RE: to ducktyping.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'option'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install option
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Generally, you want to use the Option(A) wrapper method to box
|
23
|
+
your value. This will make the right decision as to what your initial
|
24
|
+
value should be:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
foo = Option("bar")
|
28
|
+
```
|
29
|
+
|
30
|
+
This will allow you to now manipulate the value in the box via various means:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
|
34
|
+
# get the value
|
35
|
+
foo.get #=> "bar"
|
36
|
+
|
37
|
+
# return a default if the box is None
|
38
|
+
None.get_or_else { "default" } #=> "default"
|
39
|
+
|
40
|
+
# map the value to another option
|
41
|
+
foo.map { |v| v.upcase } #=> Some("BAR")
|
42
|
+
|
43
|
+
# does the value meet a requirement?
|
44
|
+
foo.exists? { |v| v == "bar" } #=> true
|
45
|
+
|
46
|
+
# return the value or nil depending on the state
|
47
|
+
foo.or_nil #=> "bar"
|
48
|
+
|
49
|
+
# chain values
|
50
|
+
foo.map { |v| v * 2 }.map { |v| v.upcase }.get_or_else { "missing" } #=> BARBAR
|
51
|
+
|
52
|
+
# attempt to extract a value but default if None
|
53
|
+
None.fold(-> { "missing" }) { |v| v.upcase } #=> missing
|
54
|
+
```
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/option.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
class Option
|
2
|
+
|
3
|
+
def or_nil
|
4
|
+
end
|
5
|
+
|
6
|
+
def ==(that)
|
7
|
+
or_nil == that.or_nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Some < Option
|
12
|
+
|
13
|
+
def initialize(value)
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_a
|
18
|
+
[get]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_or_else(&blk)
|
26
|
+
get
|
27
|
+
end
|
28
|
+
|
29
|
+
def foreach(&blk)
|
30
|
+
blk.call(get)
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def or_nil
|
36
|
+
get
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def map(&blk)
|
44
|
+
Option(blk.call(get))
|
45
|
+
end
|
46
|
+
|
47
|
+
def flat_map(&blk)
|
48
|
+
result = blk.call(get)
|
49
|
+
case result
|
50
|
+
when Option then return result
|
51
|
+
else raise TypeError, "Must be Option"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def fold(if_empty, &blk)
|
56
|
+
blk.call(get)
|
57
|
+
end
|
58
|
+
|
59
|
+
def exists?(&blk)
|
60
|
+
!! blk.call(get)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class NoneClass < Option
|
65
|
+
|
66
|
+
def to_a
|
67
|
+
[]
|
68
|
+
end
|
69
|
+
|
70
|
+
def get
|
71
|
+
raise IndexError, "None.get"
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_or_else(&blk)
|
75
|
+
blk.call
|
76
|
+
end
|
77
|
+
|
78
|
+
def foreach(&blk)
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def or_nil
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def empty?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def map(&blk)
|
91
|
+
flat_map(&blk)
|
92
|
+
end
|
93
|
+
|
94
|
+
def flat_map(&blk)
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def fold(if_empty, &blk)
|
99
|
+
if_empty.call
|
100
|
+
end
|
101
|
+
|
102
|
+
def exists?(&blk)
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
None = NoneClass.new
|
108
|
+
|
109
|
+
def Some(value)
|
110
|
+
Some.new(value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def Option(value)
|
114
|
+
value.nil? ? None : Some(value)
|
115
|
+
end
|
data/option.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/option/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Rob Ares"]
|
6
|
+
gem.email = ["rob.ares@gmail.com"]
|
7
|
+
gem.description = %q{Ruby port of Scala's Option Monad}
|
8
|
+
gem.summary = %q{Option attempts to be faithful to the useful parts of the scala api. We lose the type safety but still is quite useful when dealing with optional values.}
|
9
|
+
gem.homepage = "http://www.robares.com/"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
14
|
+
gem.name = "option"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Option::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency "rake", "= 0.9.2.2"
|
19
|
+
gem.add_development_dependency "rr", "= 1.0.4"
|
20
|
+
gem.add_development_dependency "minitest", "= 3.4.0"
|
21
|
+
end
|
data/spec/option_spec.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "minitest/spec"
|
3
|
+
|
4
|
+
require "rr"
|
5
|
+
require "rr/adapters/rr_methods"
|
6
|
+
|
7
|
+
require "option"
|
8
|
+
|
9
|
+
include RR::Adapters::RRMethods
|
10
|
+
|
11
|
+
def value
|
12
|
+
12
|
13
|
+
end
|
14
|
+
|
15
|
+
describe NoneClass do
|
16
|
+
|
17
|
+
it "#to_a returns an empty array" do
|
18
|
+
None.to_a.must_equal([])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "#get raises IndexError" do
|
22
|
+
lambda { None.get }.must_raise IndexError
|
23
|
+
end
|
24
|
+
|
25
|
+
it "#get_or_else executes the block" do
|
26
|
+
None.get_or_else { "Some" }.must_equal "Some"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "#foreach does not execute the block" do
|
30
|
+
blk = proc {}
|
31
|
+
dont_allow(blk).call
|
32
|
+
|
33
|
+
None.foreach(&blk)
|
34
|
+
|
35
|
+
RR.verify
|
36
|
+
end
|
37
|
+
|
38
|
+
it "#or_nil should return nil" do
|
39
|
+
None.or_nil.must_be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "#empty? should be true" do
|
43
|
+
None.empty?.must_equal(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "#map should return itself" do
|
47
|
+
None.map {}.must_equal(None)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "#flat_map should return itself" do
|
51
|
+
None.flat_map {}.must_equal(None)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "#exists? should return false" do
|
55
|
+
None.exists? {}.must_equal(false)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "#fold should invoke the default proc" do
|
59
|
+
None.fold(proc { value }) { |v| v.to_f }.must_equal(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be aliased to None" do
|
63
|
+
None.must_be_instance_of(NoneClass)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe Some do
|
68
|
+
|
69
|
+
it "#to_a returns the value wrapped in an array" do
|
70
|
+
Some(value).to_a.must_equal([value])
|
71
|
+
end
|
72
|
+
|
73
|
+
it "#get returns the inner value" do
|
74
|
+
Some(value).get.must_equal(value)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "#get_or_else does not execute the block;" do
|
78
|
+
blk = proc { value }
|
79
|
+
dont_allow(blk).call
|
80
|
+
|
81
|
+
Some(value).get_or_else(&blk)
|
82
|
+
|
83
|
+
RR.verify
|
84
|
+
end
|
85
|
+
|
86
|
+
it "#get_or_else returns the value" do
|
87
|
+
Some(value).get_or_else { }.must_equal(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "#foreach executes the block passing the inner value" do
|
91
|
+
blk = proc {}
|
92
|
+
mock(blk).call(value)
|
93
|
+
|
94
|
+
Some(value).foreach(&blk)
|
95
|
+
|
96
|
+
RR.verify
|
97
|
+
end
|
98
|
+
|
99
|
+
it "#or_nil should return the inner value" do
|
100
|
+
Some(value).or_nil.must_equal(value)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "#empty? should be false" do
|
104
|
+
Some(value).empty?.must_equal(false)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "#map should return the result of the proc over the value in an Option" do
|
108
|
+
Some(value).map { |v| v * 2 }.must_equal(Some(24))
|
109
|
+
end
|
110
|
+
|
111
|
+
it "#flat_map should raise TypeError if the returned value is not an Option" do
|
112
|
+
lambda { Some(value).flat_map { |v| v * 2 } }.must_raise TypeError
|
113
|
+
end
|
114
|
+
|
115
|
+
it "#flat_map should return an Option value from the block" do
|
116
|
+
Some(value).flat_map { |v| Option(v * 2) }.must_equal(Some(24))
|
117
|
+
end
|
118
|
+
|
119
|
+
it "#flat_map can return None from the block" do
|
120
|
+
Some(value).flat_map { |_| None }.must_equal(None)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "#exists? should return true when the block evaluates true" do
|
124
|
+
Some(value).exists? { |v| v % 2 == 0 }.must_equal(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "#exists? should return false when the block evaluates false" do
|
128
|
+
Some(value).exists? { |v| v % 2 != 0 }.must_equal(false)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "#fold should map the proc over the value and return it" do
|
132
|
+
Some(value).fold(proc { value * 2 }) { |v| v * 3 }.must_equal(36)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should be aliased to Some" do
|
136
|
+
Some.must_equal(Some)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should wrap the creation of a Some" do
|
140
|
+
mock(Some).new(value)
|
141
|
+
|
142
|
+
Some(value)
|
143
|
+
|
144
|
+
RR.verify
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe Option do
|
149
|
+
|
150
|
+
it "must return a some if the passed value is not nil" do
|
151
|
+
Option(value).must_equal(Some(value))
|
152
|
+
end
|
153
|
+
|
154
|
+
it "must return a None if the passed value is nil" do
|
155
|
+
Option(nil).must_equal(None)
|
156
|
+
end
|
157
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: option
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rob Ares
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70150970015480 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2.2
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70150970015480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rr
|
27
|
+
requirement: &70150970014980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - =
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.4
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70150970014980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &70150970014520 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - =
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.4.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70150970014520
|
47
|
+
description: Ruby port of Scala's Option Monad
|
48
|
+
email:
|
49
|
+
- rob.ares@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- lib/option.rb
|
60
|
+
- lib/option/version.rb
|
61
|
+
- option.gemspec
|
62
|
+
- spec/option_spec.rb
|
63
|
+
homepage: http://www.robares.com/
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.10
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Option attempts to be faithful to the useful parts of the scala api. We lose
|
87
|
+
the type safety but still is quite useful when dealing with optional values.
|
88
|
+
test_files:
|
89
|
+
- spec/option_spec.rb
|