dry-monads 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +7 -15
- data/.yardopts +4 -0
- data/CHANGELOG.md +32 -3
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +7 -1
- data/dry-monads.gemspec +3 -2
- data/lib/dry/monads.rb +50 -27
- data/lib/dry/monads/either.rb +1 -193
- data/lib/dry/monads/errors.rb +15 -0
- data/lib/dry/monads/list.rb +8 -7
- data/lib/dry/monads/maybe.rb +44 -25
- data/lib/dry/monads/result.rb +250 -0
- data/lib/dry/monads/result/fixed.rb +34 -0
- data/lib/dry/monads/right_biased.rb +79 -6
- data/lib/dry/monads/try.rb +86 -42
- data/lib/dry/monads/version.rb +1 -1
- data/lib/json/add/dry/monads/maybe.rb +1 -1
- metadata +32 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90f99efaec5f7f88c8f150a6af7d9f3ee2bc2a64
|
4
|
+
data.tar.gz: 82c6bb698fa47243c44367c14ab182d893c0b59c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 559a48263fd284e9c93c1773846b66960653a37ca811b249f5482a1cc518aa83a40e71e7adb5d206c50066293e353317793cb68893935c37f8f12a36654ad854
|
7
|
+
data.tar.gz: 8c06400c56a9b753d83df82cd011477cc29ba492a3383d1df325e9fa7eaafd56bef653590cde58c4b415994c63817eac1574b12a769267cf2060ab8c0eead8d5
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -2,32 +2,24 @@ language: ruby
|
|
2
2
|
dist: trusty
|
3
3
|
sudo: false
|
4
4
|
cache: bundler
|
5
|
-
bundler_args: --without benchmarks
|
5
|
+
bundler_args: --without benchmarks docs
|
6
6
|
script:
|
7
7
|
- bundle exec rake spec
|
8
8
|
after_success:
|
9
|
-
|
10
|
-
- '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
|
9
|
+
- '[ -d coverage ] && bundle exec codeclimate-test-reporter'
|
11
10
|
rvm:
|
12
|
-
- 2.
|
13
|
-
- 2.3.
|
14
|
-
- 2.2
|
15
|
-
-
|
16
|
-
- jruby-9.1.7.0
|
11
|
+
- 2.2.8
|
12
|
+
- 2.3.5
|
13
|
+
- 2.4.2
|
14
|
+
- jruby-9.1.13.0
|
17
15
|
- ruby-head
|
18
16
|
env:
|
19
17
|
global:
|
20
18
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
19
|
+
- COVERAGE=true
|
21
20
|
matrix:
|
22
21
|
allow_failures:
|
23
22
|
- rvm: ruby-head
|
24
|
-
- rvm: jruby-head
|
25
|
-
- rvm: rbx-3
|
26
|
-
include:
|
27
|
-
- rvm: jruby-head
|
28
|
-
before_install: gem update bundler
|
29
|
-
- rvm: rbx-3
|
30
|
-
before_install: gem update bundler
|
31
23
|
|
32
24
|
notifications:
|
33
25
|
email:
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,39 @@
|
|
1
|
+
# v0.4.0 2017-11-11
|
2
|
+
|
3
|
+
## Changed
|
4
|
+
|
5
|
+
* The `Either` monad was renamed to `Result` which sounds less nerdy but better reflects the purpose of the type. `Either::Right` became `Result::Success` and `Either::Left` became `Result::Failure`. This change is backward-compatible overall but you will see the new names when using old `Left` and `Right` methods (citizen428)
|
6
|
+
* Consequently, `Try::Success` and `Try::Failure` were renamed to `Try::Value` and `Try::Error` (flash-gordon)
|
7
|
+
|
8
|
+
## Added
|
9
|
+
|
10
|
+
* `Try#or`, works as `Result#or` (flash-gordon)
|
11
|
+
* `Maybe#success?` and `Maybe#failure?` (aliases for `#some?` and `#none?`) (flash-gordon)
|
12
|
+
* `Either#flip` inverts a `Result` value (flash-gordon)
|
13
|
+
* `List#map` called without a block returns an `Enumerator` object (flash-gordon)
|
14
|
+
* Right-biased monads (`Maybe`, `Result`, and `Try`) now implement the `===` operator which is used for equality checks in the `case` statement (flash-gordon)
|
15
|
+
```ruby
|
16
|
+
case value
|
17
|
+
when Some(1..100) then :ok
|
18
|
+
when Some { |x| x < 0 } then :negative
|
19
|
+
when Some(Integer) then :invalid
|
20
|
+
else raise TypeError
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
## Deprecated
|
25
|
+
|
26
|
+
* Direct accessing `value` on right-biased monads has been deprecated, use the `value!` method instead. `value!` will raise an exception if it is called on a Sailure/None/Error instance (flash-gordon)
|
27
|
+
|
28
|
+
[Compare v0.3.1...v0.4.0](https://github.com/dry-rb/dry-monads/compare/v0.3.1...v0.4.0)
|
29
|
+
|
1
30
|
# v0.3.1 2017-03-18
|
2
31
|
|
3
32
|
## Fixed
|
4
33
|
|
5
34
|
* Fixed unexpected coercing to `Hash` on `.bind` call (flash-gordon)
|
6
35
|
|
7
|
-
[Compare v0.3.
|
36
|
+
[Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-monads/compare/v0.3.0...v0.3.1)
|
8
37
|
|
9
38
|
# v0.3.0 2017-03-16
|
10
39
|
|
@@ -17,7 +46,7 @@
|
|
17
46
|
* Added `List#traverse` that "flips" the list with an embedded monad (flash-gordon + damncabbage)
|
18
47
|
* Added `#tee` for all right-biased monads (flash-gordon)
|
19
48
|
|
20
|
-
[Compare v0.
|
49
|
+
[Compare v0.2.1...v0.3.0](https://github.com/dry-rb/dry-monads/compare/v0.2.1...v0.3.0)
|
21
50
|
|
22
51
|
# v0.2.1 2016-11-13
|
23
52
|
|
@@ -30,7 +59,7 @@
|
|
30
59
|
* `Right(nil).to_maybe` now returns `None` with a warning instead of failing (orisaka)
|
31
60
|
* `Some#value_or` doesn't require an argument because `None#value_or` doesn't require it either if a block was passed (flash-gordon)
|
32
61
|
|
33
|
-
[Compare v0.2.
|
62
|
+
[Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-monads/compare/v0.2.0...v0.2.1)
|
34
63
|
|
35
64
|
# v0.2.0 2016-09-18
|
36
65
|
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Issue Guidelines
|
2
|
+
|
3
|
+
## Reporting bugs
|
4
|
+
|
5
|
+
If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
|
6
|
+
|
7
|
+
## Reporting feature requests
|
8
|
+
|
9
|
+
Report a feature request **only after discourseing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discourseion thread, and instead summarize what was discourseed.
|
10
|
+
|
11
|
+
## Reporting questions, support requests, ideas, concerns etc.
|
12
|
+
|
13
|
+
**PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
|
14
|
+
|
15
|
+
# Pull Request Guidelines
|
16
|
+
|
17
|
+
A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
|
18
|
+
|
19
|
+
Other requirements:
|
20
|
+
|
21
|
+
1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
|
22
|
+
2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
|
23
|
+
3) Add API documentation if it's a new feature
|
24
|
+
4) Update API documentation if it changes an existing feature
|
25
|
+
5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
|
26
|
+
|
27
|
+
# Asking for help
|
28
|
+
|
29
|
+
If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org).
|
data/Gemfile
CHANGED
data/dry-monads.gemspec
CHANGED
@@ -25,11 +25,12 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.bindir = 'exe'
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ['lib']
|
28
|
-
spec.required_ruby_version = ">= 2.
|
28
|
+
spec.required_ruby_version = ">= 2.2.0"
|
29
29
|
spec.add_dependency 'dry-equalizer'
|
30
|
-
spec.add_dependency 'dry-core'
|
30
|
+
spec.add_dependency 'dry-core', '~> 0.3', '>= 0.3.3'
|
31
31
|
|
32
32
|
spec.add_development_dependency 'bundler'
|
33
33
|
spec.add_development_dependency 'rake'
|
34
34
|
spec.add_development_dependency 'rspec'
|
35
|
+
spec.add_development_dependency 'dry-types', '>= 0.12'
|
35
36
|
end
|
data/lib/dry/monads.rb
CHANGED
@@ -1,43 +1,66 @@
|
|
1
|
-
require 'dry/
|
1
|
+
require 'dry/core/constants'
|
2
2
|
require 'dry/monads/maybe'
|
3
3
|
require 'dry/monads/try'
|
4
4
|
require 'dry/monads/list'
|
5
|
+
require 'dry/monads/result'
|
6
|
+
require 'dry/monads/result/fixed'
|
5
7
|
|
6
8
|
module Dry
|
7
9
|
# @api public
|
8
10
|
module Monads
|
9
|
-
|
11
|
+
Undefined = Dry::Core::Constants::Undefined
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# @return [Maybe::Some, Maybe::None]
|
16
|
-
def Maybe(value)
|
17
|
-
Maybe.lift(value)
|
18
|
-
end
|
13
|
+
CONSTRUCTORS = [
|
14
|
+
Maybe::Mixin::Constructors,
|
15
|
+
Result::Mixin::Constructors
|
16
|
+
].freeze
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
18
|
+
Some = Maybe::Some
|
19
|
+
None = Maybe::None
|
20
|
+
Success = Result::Success
|
21
|
+
Failure = Result::Failure
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
extend(*CONSTRUCTORS)
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
super
|
30
27
|
|
31
|
-
|
32
|
-
# @return [Either::Right]
|
33
|
-
def Right(value)
|
34
|
-
Either::Right.new(value)
|
28
|
+
base.include(*CONSTRUCTORS)
|
35
29
|
end
|
36
30
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
|
31
|
+
# Creates a module that has two methods: `Success` and `Failure`.
|
32
|
+
# `Success` is identical to {Result::Mixin::Constructors#Success} and Failure
|
33
|
+
# rejects values that don't conform the value of the `error`
|
34
|
+
# parameter. This is essentially a Result type with the `Failure` part
|
35
|
+
# fixed.
|
36
|
+
#
|
37
|
+
# @example using dry-types
|
38
|
+
# module Types
|
39
|
+
# include Dry::Types.module
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Operation
|
43
|
+
# # :user_not_found and :account_not_found are the only
|
44
|
+
# # values allowed as failure results
|
45
|
+
# Error =
|
46
|
+
# Types.Value(:user_not_found) |
|
47
|
+
# Types.Value(:account_not_found)
|
48
|
+
#
|
49
|
+
# def find_account(id)
|
50
|
+
# account = acount_repo.find(id)
|
51
|
+
#
|
52
|
+
# account ? Success(account) : Failure(:account_not_found)
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def find_user(id)
|
56
|
+
# # ...
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# @param error [#===] the type of allowed failures
|
61
|
+
# @return [Module]
|
62
|
+
def self.Result(error, **options)
|
63
|
+
Result::Fixed[error, **options]
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
data/lib/dry/monads/either.rb
CHANGED
@@ -1,193 +1 @@
|
|
1
|
-
require 'dry/
|
2
|
-
|
3
|
-
require 'dry/monads/right_biased'
|
4
|
-
require 'dry/monads/transformer'
|
5
|
-
|
6
|
-
module Dry
|
7
|
-
module Monads
|
8
|
-
# Represents a value which is either correct or an error.
|
9
|
-
#
|
10
|
-
# @api public
|
11
|
-
class Either
|
12
|
-
include Dry::Equalizer(:right, :left)
|
13
|
-
include Transformer
|
14
|
-
|
15
|
-
attr_reader :right, :left
|
16
|
-
|
17
|
-
class << self
|
18
|
-
# Wraps the given value with Right
|
19
|
-
#
|
20
|
-
# @param value [Object] the value to be stored inside Right
|
21
|
-
# @return [Either::Right]
|
22
|
-
def pure(value)
|
23
|
-
Right.new(value)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns self, added to keep the interface compatible with other monads.
|
28
|
-
#
|
29
|
-
# @return [Either::Right, Either::Left]
|
30
|
-
def to_either
|
31
|
-
self
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns the Either monad.
|
35
|
-
# This is how we're doing polymorphism in Ruby 😕
|
36
|
-
#
|
37
|
-
# @return [Monad]
|
38
|
-
def monad
|
39
|
-
Either
|
40
|
-
end
|
41
|
-
|
42
|
-
# Represents a value that is in a correct state, i.e. everything went right.
|
43
|
-
#
|
44
|
-
# @api public
|
45
|
-
class Right < Either
|
46
|
-
include RightBiased::Right
|
47
|
-
|
48
|
-
alias value right
|
49
|
-
|
50
|
-
# @param right [Object] a value in a correct state
|
51
|
-
def initialize(right)
|
52
|
-
@right = right
|
53
|
-
end
|
54
|
-
|
55
|
-
# Apply the second function to value.
|
56
|
-
#
|
57
|
-
# @api public
|
58
|
-
def either(_, f)
|
59
|
-
f.call(value)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns false
|
63
|
-
def left?
|
64
|
-
false
|
65
|
-
end
|
66
|
-
alias failure? left?
|
67
|
-
|
68
|
-
# Returns true
|
69
|
-
def right?
|
70
|
-
true
|
71
|
-
end
|
72
|
-
alias success? right?
|
73
|
-
|
74
|
-
# Does the same thing as #bind except it also wraps the value
|
75
|
-
# in an instance of Either::Right monad. This allows for easier
|
76
|
-
# chaining of calls.
|
77
|
-
#
|
78
|
-
# @example
|
79
|
-
# Dry::Monads.Right(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Right(25)
|
80
|
-
#
|
81
|
-
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
82
|
-
# @return [Either::Right]
|
83
|
-
def fmap(*args, &block)
|
84
|
-
Right.new(bind(*args, &block))
|
85
|
-
end
|
86
|
-
|
87
|
-
# @return [String]
|
88
|
-
def to_s
|
89
|
-
"Right(#{value.inspect})"
|
90
|
-
end
|
91
|
-
alias inspect to_s
|
92
|
-
|
93
|
-
# @return [Maybe::Some]
|
94
|
-
def to_maybe
|
95
|
-
Kernel.warn 'Right(nil) transformed to None' if value.nil?
|
96
|
-
Dry::Monads::Maybe(value)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Represents a value that is in an incorrect state, i.e. something went wrong.
|
101
|
-
#
|
102
|
-
# @api public
|
103
|
-
class Left < Either
|
104
|
-
include RightBiased::Left
|
105
|
-
|
106
|
-
alias value left
|
107
|
-
|
108
|
-
# @param left [Object] a value in an error state
|
109
|
-
def initialize(left)
|
110
|
-
@left = left
|
111
|
-
end
|
112
|
-
|
113
|
-
# Apply the first function to value.
|
114
|
-
#
|
115
|
-
# @api public
|
116
|
-
def either(f, _)
|
117
|
-
f.call(value)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns true
|
121
|
-
def left?
|
122
|
-
true
|
123
|
-
end
|
124
|
-
alias failure? left?
|
125
|
-
|
126
|
-
# Returns false
|
127
|
-
def right?
|
128
|
-
false
|
129
|
-
end
|
130
|
-
alias success? right?
|
131
|
-
|
132
|
-
# If a block is given passes internal value to it and returns the result,
|
133
|
-
# otherwise simply returns the parameter val.
|
134
|
-
#
|
135
|
-
# @example
|
136
|
-
# Dry::Monads.Left(ArgumentError.new('error message')).or(&:message) # => "error message"
|
137
|
-
#
|
138
|
-
# @param args [Array<Object>] arguments that will be passed to a block
|
139
|
-
# if one was given, otherwise the first
|
140
|
-
# value will be returned
|
141
|
-
# @return [Object]
|
142
|
-
def or(*args)
|
143
|
-
if block_given?
|
144
|
-
yield(value, *args)
|
145
|
-
else
|
146
|
-
args[0]
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# A lifted version of `#or`. Wraps the passed value or the block result with Either::Right.
|
151
|
-
#
|
152
|
-
# @example
|
153
|
-
# Dry::Monads.Left.new('no value').or_fmap('value') # => Right("value")
|
154
|
-
# Dry::Monads.Left.new('no value').or_fmap { 'value' } # => Right("value")
|
155
|
-
#
|
156
|
-
# @param args [Array<Object>] arguments will be passed to the underlying `#or` call
|
157
|
-
# @return [Either::Right] Wrapped value
|
158
|
-
def or_fmap(*args, &block)
|
159
|
-
Right.new(self.or(*args, &block))
|
160
|
-
end
|
161
|
-
|
162
|
-
# @return [String]
|
163
|
-
def to_s
|
164
|
-
"Left(#{value.inspect})"
|
165
|
-
end
|
166
|
-
alias inspect to_s
|
167
|
-
|
168
|
-
# @return [Maybe::None]
|
169
|
-
def to_maybe
|
170
|
-
Maybe::None.instance
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# A module that can be included for easier access to Either monads.
|
175
|
-
module Mixin
|
176
|
-
Right = Right
|
177
|
-
Left = Left
|
178
|
-
|
179
|
-
# @param value [Object] the value to be stored in the monad
|
180
|
-
# @return [Either::Right]
|
181
|
-
def Right(value)
|
182
|
-
Right.new(value)
|
183
|
-
end
|
184
|
-
|
185
|
-
# @param value [Object] the value to be stored in the monad
|
186
|
-
# @return [Either::Left]
|
187
|
-
def Left(value)
|
188
|
-
Left.new(value)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
1
|
+
require 'dry/monads/result'
|