divergent 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/Rakefile +10 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/divergent.gemspec +24 -0
- data/lib/divergent.rb +13 -0
- data/lib/divergent/errors.rb +7 -0
- data/lib/divergent/maybe.rb +248 -0
- data/lib/divergent/monad.rb +20 -0
- data/lib/divergent/try.rb +296 -0
- data/lib/divergent/version.rb +3 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7ba0cd2b5c2f7fe954da0d3d7606369605b4d258
|
4
|
+
data.tar.gz: f8647ef8978718d7e1f6723adf57972e571361ad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d30e7e48cc2e25382f685b392c8bf06d018cdfffcaaa16289993bd61d72e24963824cd5eb1dbd3607aea77559a31d054bf2f57e9f6dbff8f4d91070796a25bfa
|
7
|
+
data.tar.gz: 480627d46e138a0715a4e7966b5e49f05059730a0911af63840f60ce296e725d708e240a30c08e823d9b0623473c4b862f1867eef855044a8b33d2b2cc5747a4
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Cao Jiafeng
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Divergent
|
2
|
+
|
3
|
+
A collection of monads for handling error in ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'divergent'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install divergent
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Using `Try`:
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
require 'divergent'
|
27
|
+
|
28
|
+
include Divergent
|
29
|
+
|
30
|
+
require 'uri'
|
31
|
+
def parse_url(url)
|
32
|
+
Try {
|
33
|
+
URI.parse url
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
success_url = parse_url("http://www.google.com") # => Success<http://www.google.com>
|
38
|
+
|
39
|
+
failed_url = parse_url(':google.com') #=> Failure<bad URI(is not URI?): :google.com>
|
40
|
+
|
41
|
+
### get_or_else
|
42
|
+
|
43
|
+
failed_url.get_or_else URI('http://duckduckgo.com') #=> #<URI::HTTP http://duckduckgo.com>
|
44
|
+
|
45
|
+
### chainable operations
|
46
|
+
|
47
|
+
# map
|
48
|
+
success_url.map(&:scheme) #=> Success<http>
|
49
|
+
|
50
|
+
failed_url.map(&:scheme) #=> Failure<bad URI(is not URI?): :google.com>
|
51
|
+
|
52
|
+
# fmap
|
53
|
+
parse_url("http://thisisnotagoodsite.com").fmap do |url|
|
54
|
+
Try { Net::HTTP.get(url) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# each
|
58
|
+
parse_url("http://google.com").each { |url| p url.to_s }
|
59
|
+
# =>
|
60
|
+
# "http://google.com"
|
61
|
+
|
62
|
+
# filter
|
63
|
+
parse_url("http://google.com").filter { |url| url.scheme == "http" } #=> Success<http://google.com>
|
64
|
+
parse_url("http://google.com").filter { |url| url.scheme == "https" } #=> Failure<Predicate does not hold for http://google.com>
|
65
|
+
|
66
|
+
# recover from error
|
67
|
+
failed_url.recover do |error|
|
68
|
+
case error
|
69
|
+
when NoMethodError
|
70
|
+
:no_method
|
71
|
+
when StandardError
|
72
|
+
:standard_error
|
73
|
+
else
|
74
|
+
:others
|
75
|
+
end
|
76
|
+
end #=> Success<standard_error>
|
77
|
+
```
|
78
|
+
|
79
|
+
|
80
|
+
`Maybe` has similar interface to `Try`.
|
81
|
+
`Maybe` uses `None` instead of `Failure`.
|
82
|
+
|
83
|
+
``` ruby
|
84
|
+
# provide default value
|
85
|
+
user = Maybe.unit User.find('a_non_exist_id')
|
86
|
+
user.get_or_else(default_user)
|
87
|
+
user.or_else(Maybe.unit User.find_by_name("username"))
|
88
|
+
|
89
|
+
# usage as a collection
|
90
|
+
user.each { |u| p u}
|
91
|
+
user.map(&:age)
|
92
|
+
user.fmap { |u| Maybe(u.gender) }
|
93
|
+
user.filter { |u| u.age > 20 }
|
94
|
+
```
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lerencao/divergent.rb.
|
99
|
+
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
104
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/divergent.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'divergent/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "divergent"
|
8
|
+
spec.version = Divergent::VERSION
|
9
|
+
spec.authors = ["Cao Jiafeng"]
|
10
|
+
spec.email = ["funfriendcjf@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{a collection of monad implemented in ruby inspired by scala}
|
13
|
+
spec.description = %q{the collection provides class handling errors in ruby}
|
14
|
+
spec.homepage = "http://github.com/lerencao/devergent.rb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "pry"
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
end
|
data/lib/divergent.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'divergent/version'
|
2
|
+
require 'divergent/try'
|
3
|
+
require 'divergent/maybe'
|
4
|
+
|
5
|
+
|
6
|
+
##
|
7
|
+
# Divergent is a collection of monad class to do error handling in Ruby.
|
8
|
+
#
|
9
|
+
# Currently, it only contains two class:
|
10
|
+
# 1. Try: a container which can wraps possible errors.
|
11
|
+
# 2. Maybe: a container which wraps nil case.
|
12
|
+
module Divergent
|
13
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'monad'
|
5
|
+
|
6
|
+
module Divergent
|
7
|
+
##
|
8
|
+
# Represents optional values. Instances of Maybe
|
9
|
+
# are either an instance of Some or the object None.
|
10
|
+
#
|
11
|
+
# The most idiomatic way to use an Maybe instance is to treat it
|
12
|
+
# as a collection or monad and use `map`, `fmap`, `filter`, or
|
13
|
+
# `each`.
|
14
|
+
class Maybe
|
15
|
+
include Monad
|
16
|
+
|
17
|
+
##
|
18
|
+
# An Maybe factory which return None.
|
19
|
+
# This always return the same None object.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# ```
|
24
|
+
# Maybe.empty == Maybe.empty
|
25
|
+
# ```
|
26
|
+
def self.empty
|
27
|
+
None
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# An factory which creates Some(v) if the argument is not nil,
|
32
|
+
# and None if it is nil.
|
33
|
+
#
|
34
|
+
# Examples:
|
35
|
+
#
|
36
|
+
# ```
|
37
|
+
# Maybe.unit(1) # => Some(1)
|
38
|
+
# Maybe.unit(nil) # => None
|
39
|
+
# ```
|
40
|
+
def self.unit(v)
|
41
|
+
if v.nil?
|
42
|
+
None
|
43
|
+
else
|
44
|
+
Some.new(v)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Returns the result of applying block to this Maybe value,
|
50
|
+
# if this value is not empty.
|
51
|
+
# Return `None` if this value is empty.
|
52
|
+
#
|
53
|
+
# Slightly different from `map` in that block is expected to
|
54
|
+
# return an instance of `Maybe`.
|
55
|
+
#
|
56
|
+
# Examples
|
57
|
+
#
|
58
|
+
# ```
|
59
|
+
# Maybe.unit(1).fmap { |v| Maybe.unit(v + 1) } # => Some(2)
|
60
|
+
# some_hash = {}
|
61
|
+
# Maybe.unit(:a).fmap { |v| Maybe.unit(some_hash[v]) } # => None
|
62
|
+
# ```
|
63
|
+
def fmap() # :yields: v
|
64
|
+
if empty?
|
65
|
+
None
|
66
|
+
else
|
67
|
+
yield(get)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Return true if the maybe is None, false otherwise.
|
73
|
+
def empty?
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Return the maybe's value.
|
79
|
+
#
|
80
|
+
# Notes:
|
81
|
+
# the maybe should not be empty.
|
82
|
+
#
|
83
|
+
# otherwise, raise Standarderror.
|
84
|
+
def get
|
85
|
+
raise NotImplementedError
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Returns the maybe's value if the maybe is nonempty, otherwise
|
90
|
+
# return the `v`.
|
91
|
+
def get_or_else(v)
|
92
|
+
if empty?
|
93
|
+
v
|
94
|
+
else
|
95
|
+
get
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Return this maybe id it is not empty,
|
101
|
+
# else return the `v`.
|
102
|
+
#
|
103
|
+
# Note:
|
104
|
+
# This is similar to Maybe#get_or_else,
|
105
|
+
# but the v should be an instance of Maybe.
|
106
|
+
def or_else(v)
|
107
|
+
if empty?
|
108
|
+
v
|
109
|
+
else
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Return a Maybe containing the result of applying block to the maybe's value
|
116
|
+
# if not empty.
|
117
|
+
# Otherwise, return None.
|
118
|
+
|
119
|
+
# Notes:
|
120
|
+
# This is similar to +flat_map+ except here,
|
121
|
+
# block does not need to wrap its result in a maybe.
|
122
|
+
def map() # :yields: v
|
123
|
+
if empty?
|
124
|
+
None
|
125
|
+
else
|
126
|
+
Maybe.unit(yield get)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
##
|
132
|
+
# Return this maybe if it is not empty and the predicate block evals to true.
|
133
|
+
# Otherwise, return None.
|
134
|
+
def filter() # :yields: v
|
135
|
+
if !empty? && yield(get)
|
136
|
+
self
|
137
|
+
else
|
138
|
+
None
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Test whether the maybe contains a given elem.
|
144
|
+
#
|
145
|
+
# Examples:
|
146
|
+
#
|
147
|
+
# ```
|
148
|
+
# Maybe.unit(1).include?(1) #=> true
|
149
|
+
# Maybe.unit(1).include?(2) #=> false
|
150
|
+
# Maybe.empty.include?(1) #=> false
|
151
|
+
# ```
|
152
|
+
def include?(elem)
|
153
|
+
!empty? && get == elem
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Return true if it is not empty and the predicate block evals to true.
|
158
|
+
#
|
159
|
+
# Examples:
|
160
|
+
#
|
161
|
+
# ```
|
162
|
+
# Maybe.unit(1).any?{ |v| v == 1} #=> true
|
163
|
+
# Maybe.unit(1).any?{ |v| v == 2} #=> false
|
164
|
+
# Maybe.empty.any?{ |v| v == 1} #=> false
|
165
|
+
# ```
|
166
|
+
def any?() # :yields: v
|
167
|
+
!empty? && yield(get)
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Return true if it is empty or the predicate block evals to true
|
172
|
+
# when applying to the maybe's value.
|
173
|
+
#
|
174
|
+
# Examples:
|
175
|
+
#
|
176
|
+
# ```
|
177
|
+
# Maybe.unit(1).all?{ |v| v == 1} #=> true
|
178
|
+
# Maybe.unit(1).all?{ |v| v == 2} #=> false
|
179
|
+
# Maybe.empty.all?{ |v| v == 1} #=> true
|
180
|
+
# ```
|
181
|
+
def all?() # :yields: v
|
182
|
+
empty? || yield(get)
|
183
|
+
end
|
184
|
+
##
|
185
|
+
# Apply the given block to the maybe's value if not empty.
|
186
|
+
# Otherwise, do nothing.
|
187
|
+
def each() # :yields: v
|
188
|
+
yield(get) unless empty?
|
189
|
+
end
|
190
|
+
|
191
|
+
## Returns a singleton list containing the maybe's value
|
192
|
+
# if it is nonempty, or the empty list if empty.
|
193
|
+
def to_a
|
194
|
+
if empty?
|
195
|
+
[]
|
196
|
+
else
|
197
|
+
[get]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class Some < Maybe # :nodoc: all
|
203
|
+
def initialize(v)
|
204
|
+
raise 'value cannot be nil' if v.nil?
|
205
|
+
@v = v
|
206
|
+
end
|
207
|
+
|
208
|
+
def empty?
|
209
|
+
false
|
210
|
+
end
|
211
|
+
|
212
|
+
def get
|
213
|
+
@v
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_s
|
217
|
+
"Some(#{@v.inspect})"
|
218
|
+
end
|
219
|
+
|
220
|
+
alias inspect to_s
|
221
|
+
end
|
222
|
+
|
223
|
+
None = Class.new(Maybe) do # :nodoc: all
|
224
|
+
include Singleton
|
225
|
+
|
226
|
+
def empty?
|
227
|
+
true
|
228
|
+
end
|
229
|
+
|
230
|
+
def get
|
231
|
+
raise NoSuchElementError, 'no such element in None.get'
|
232
|
+
end
|
233
|
+
|
234
|
+
def to_s
|
235
|
+
"None"
|
236
|
+
end
|
237
|
+
|
238
|
+
alias inspect to_s
|
239
|
+
end.instance.freeze
|
240
|
+
end
|
241
|
+
|
242
|
+
module Divergent
|
243
|
+
def Maybe(v)
|
244
|
+
Maybe.unit(v)
|
245
|
+
end
|
246
|
+
|
247
|
+
module_function :Maybe
|
248
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Divergent
|
2
|
+
##
|
3
|
+
# The module defines the interfaces that other class should implement.
|
4
|
+
#
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# ```
|
8
|
+
# Maybe.unit(1) # => Some(1)
|
9
|
+
# Maybe.unit(1).fmap { |v| v + 1 } => Some(2)
|
10
|
+
# ```
|
11
|
+
module Monad
|
12
|
+
def self.unit(v)
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def fmap(&f)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require_relative 'monad'
|
3
|
+
|
4
|
+
module Divergent
|
5
|
+
##
|
6
|
+
# The `Try` type represents a computation that
|
7
|
+
# may either result in an exception, or return a
|
8
|
+
# successfully computed value.
|
9
|
+
# It's similar to, but semantically different from Either.
|
10
|
+
#
|
11
|
+
# Instances of Try, are either an instance of Success or Failure.
|
12
|
+
#
|
13
|
+
# For example, `Try` can be used to perform division on a user-defined input,
|
14
|
+
# without the need to do explicit exception-handling in
|
15
|
+
# all of the places that an exception might occur.
|
16
|
+
module Try
|
17
|
+
include Monad
|
18
|
+
def self.unit(v)
|
19
|
+
Success.new(v)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns `true` if the `Try` is a `Failure`, `false` otherwise.
|
23
|
+
def failure?
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns `true` if the `Try` is a `Success`, `false` otherwise.
|
28
|
+
def success?
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the value from this `Success`
|
33
|
+
# or the given `default` argument if this is a `Failure`.
|
34
|
+
def get_or_else(default)
|
35
|
+
if success?
|
36
|
+
get
|
37
|
+
else
|
38
|
+
default
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns this `Try` if it's a `Success`
|
43
|
+
# or the given `default` argument if this is a `Failure`.
|
44
|
+
#
|
45
|
+
# Notes: the `default` value should be an instance of Try.
|
46
|
+
def or_else(default)
|
47
|
+
if success?
|
48
|
+
self
|
49
|
+
else
|
50
|
+
default
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the value from this `Success`
|
55
|
+
# or throws the exception if this is a `Failure`.
|
56
|
+
def get
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Applies the given block if this is a `Success`,
|
62
|
+
# otherwise returns `Unit` if this is a `Failure`.
|
63
|
+
#
|
64
|
+
# Notes:
|
65
|
+
#
|
66
|
+
# If block throws, then this method may throw an exception.
|
67
|
+
def each(&block)
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Maps the given function to the value from this `Success`
|
73
|
+
# or returns this if this is a `Failure`.
|
74
|
+
def map(&block)
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
# Converts this to a `Failure` if the predicate is not satisfied.
|
79
|
+
def filter(&block)
|
80
|
+
raise NotImplementedError
|
81
|
+
end
|
82
|
+
|
83
|
+
# Applies the given block if this is a `Failure`,
|
84
|
+
# otherwise returns this if this is a `Success`.
|
85
|
+
#
|
86
|
+
# Notes: block call should return an instance of Try.
|
87
|
+
# This is like `fmap` for the exception.
|
88
|
+
def recover_with(&block)
|
89
|
+
raise NotImplementedError
|
90
|
+
end
|
91
|
+
|
92
|
+
# Applies the given block if this is a `Failure`,
|
93
|
+
# otherwise returns this if this is a `Success`.
|
94
|
+
#
|
95
|
+
# This is like `fmap` for the exception.
|
96
|
+
def recover(&block)
|
97
|
+
raise NotImplementedError
|
98
|
+
end
|
99
|
+
|
100
|
+
# Transforms a nested `Try` into an un-nested `Try`.
|
101
|
+
def flatten
|
102
|
+
raise NotImplementedError
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Inverts this `Try`. If this is a `Failure`, returns its exception wrapped in a `Success`.
|
107
|
+
# If this is a `Success`, returns a `Failure` containing an UnSupportedOperationError.
|
108
|
+
def failed
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Completes this `Try` by applying the function `f` to this if this is of type `Failure`,
|
114
|
+
# or conversely, by applying `s` if this is a `Success`.
|
115
|
+
def transform(s, f)
|
116
|
+
raise NotImplementedError
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Success # :nodoc: true
|
121
|
+
include Try
|
122
|
+
|
123
|
+
def initialize(value)
|
124
|
+
@value = value
|
125
|
+
end
|
126
|
+
|
127
|
+
def fmap()
|
128
|
+
begin
|
129
|
+
yield @value
|
130
|
+
rescue => e
|
131
|
+
Failure.new(e)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def success?
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
139
|
+
def failure?
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def get
|
144
|
+
@value
|
145
|
+
end
|
146
|
+
|
147
|
+
def each()
|
148
|
+
yield(@value)
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
def map()
|
153
|
+
t = begin
|
154
|
+
yield(@value)
|
155
|
+
rescue => e
|
156
|
+
Failure.new(e)
|
157
|
+
end
|
158
|
+
|
159
|
+
Success.new(t)
|
160
|
+
end
|
161
|
+
|
162
|
+
def filter()
|
163
|
+
p = begin
|
164
|
+
yield(@value)
|
165
|
+
rescue => e
|
166
|
+
return Failure.new(e)
|
167
|
+
end
|
168
|
+
if p
|
169
|
+
self
|
170
|
+
else
|
171
|
+
Failure.new(NoSuchElementError.new("Predicate does not hold for #{@value}"))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def recover_with(&block)
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
def recover(&block)
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
def failed
|
184
|
+
Failure.new(UnSupportedOperationError.new('Success.failed'))
|
185
|
+
end
|
186
|
+
|
187
|
+
def transform(s, _f)
|
188
|
+
begin
|
189
|
+
s.call(@value)
|
190
|
+
rescue => e
|
191
|
+
Failure.new(e)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def flatten
|
196
|
+
if @value.is_a? Try
|
197
|
+
@value
|
198
|
+
else
|
199
|
+
self
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def to_s
|
204
|
+
"Success<#{@value}>"
|
205
|
+
end
|
206
|
+
|
207
|
+
alias inspect to_s
|
208
|
+
end
|
209
|
+
|
210
|
+
class Failure # :nodoc: true
|
211
|
+
include Try
|
212
|
+
def initialize(error)
|
213
|
+
raise 'error should be an StandardError' unless error.is_a? StandardError
|
214
|
+
@error = error
|
215
|
+
end
|
216
|
+
|
217
|
+
def fmap()
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
def failure?
|
222
|
+
true
|
223
|
+
end
|
224
|
+
|
225
|
+
def success?
|
226
|
+
false
|
227
|
+
end
|
228
|
+
|
229
|
+
def get
|
230
|
+
raise @error
|
231
|
+
end
|
232
|
+
|
233
|
+
def each(&block)
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
|
237
|
+
def map(&block)
|
238
|
+
self
|
239
|
+
end
|
240
|
+
|
241
|
+
def filter(&block)
|
242
|
+
self
|
243
|
+
end
|
244
|
+
|
245
|
+
def recover_with()
|
246
|
+
begin
|
247
|
+
yield(@error)
|
248
|
+
rescue => e
|
249
|
+
Failure.new(e)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def recover()
|
254
|
+
t = yield(@error)
|
255
|
+
Success.new(t)
|
256
|
+
rescue => e
|
257
|
+
return Failure.new(e)
|
258
|
+
end
|
259
|
+
|
260
|
+
def failed
|
261
|
+
Success.new(@error)
|
262
|
+
end
|
263
|
+
|
264
|
+
def transform(_s, f)
|
265
|
+
begin
|
266
|
+
f.call(@error)
|
267
|
+
rescue => e
|
268
|
+
Failure.new(e)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def flatten
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
"Failure<#{@error}>"
|
278
|
+
end
|
279
|
+
|
280
|
+
alias inspect to_s
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
module Divergent
|
286
|
+
##
|
287
|
+
# Constructs a `Try` by calling the passed block. This
|
288
|
+
# method will ensure any StandardError is caught and a
|
289
|
+
# `Failure` object is returned.
|
290
|
+
def Try
|
291
|
+
Success.new(yield)
|
292
|
+
rescue => e
|
293
|
+
Failure.new(e)
|
294
|
+
end
|
295
|
+
module_function :Try
|
296
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: divergent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cao Jiafeng
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pry
|
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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description: the collection provides class handling errors in ruby
|
70
|
+
email:
|
71
|
+
- funfriendcjf@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- divergent.gemspec
|
85
|
+
- lib/divergent.rb
|
86
|
+
- lib/divergent/errors.rb
|
87
|
+
- lib/divergent/maybe.rb
|
88
|
+
- lib/divergent/monad.rb
|
89
|
+
- lib/divergent/try.rb
|
90
|
+
- lib/divergent/version.rb
|
91
|
+
homepage: http://github.com/lerencao/devergent.rb
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.4.5.1
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: a collection of monad implemented in ruby inspired by scala
|
115
|
+
test_files: []
|