dry-monads 0.3.1 → 0.4.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 +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'
|