dry-monads 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -10
- data/CHANGELOG.md +67 -0
- data/Gemfile +1 -1
- data/bin/console +2 -0
- data/lib/dry/monads.rb +1 -0
- data/lib/dry/monads/either.rb +67 -69
- data/lib/dry/monads/list.rb +285 -0
- data/lib/dry/monads/maybe.rb +49 -71
- data/lib/dry/monads/right_biased.rb +148 -0
- data/lib/dry/monads/transformer.rb +42 -0
- data/lib/dry/monads/try.rb +25 -31
- data/lib/dry/monads/version.rb +1 -1
- metadata +7 -5
- data/.rubocop.yml +0 -64
- data/.rubocop_todo.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d763397f947c61f8b46fa0997e72b24b0c6b896
|
4
|
+
data.tar.gz: 99c80e46741e351e19f107dd71c01e8a7a66beaf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 134d7bc216b65df0b1ed974fc68d04521ec1766c527848c4df473057ae5175e42447cee84073e69b44d4fe3a8a9399918ffb8c7119d6eb65abf0f7aa873d521c
|
7
|
+
data.tar.gz: b25676d21087677f2a2276b0e09abb3e672ce0d12f4c50aeee88c72afa8aff0d4e6df70ace29efc00b699094b94fe44f14753063d3f63bc2653bc8c50839d5d5
|
data/.travis.yml
CHANGED
@@ -1,25 +1,23 @@
|
|
1
1
|
language: ruby
|
2
2
|
dist: trusty
|
3
|
-
sudo:
|
3
|
+
sudo: false
|
4
4
|
cache: bundler
|
5
5
|
bundler_args: --without benchmarks
|
6
6
|
script:
|
7
7
|
- bundle exec rake spec
|
8
|
-
- bundle exec rubocop
|
9
8
|
after_success:
|
10
|
-
|
9
|
+
# Send coverage report from the job #1 == current MRI release
|
10
|
+
- '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
|
11
11
|
rvm:
|
12
|
+
- 2.4.0
|
13
|
+
- 2.3.3
|
14
|
+
- 2.2.6
|
12
15
|
- 2.1.10
|
13
|
-
-
|
14
|
-
- 2.3.1
|
15
|
-
- jruby-9.1.5.0
|
16
|
+
- jruby-9.1.7.0
|
16
17
|
- ruby-head
|
17
|
-
- rbx-3
|
18
18
|
env:
|
19
19
|
global:
|
20
20
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
21
|
-
before_install:
|
22
|
-
- gem update bundler
|
23
21
|
matrix:
|
24
22
|
allow_failures:
|
25
23
|
- rvm: ruby-head
|
@@ -27,7 +25,9 @@ matrix:
|
|
27
25
|
- rvm: rbx-3
|
28
26
|
include:
|
29
27
|
- rvm: jruby-head
|
30
|
-
before_install: gem
|
28
|
+
before_install: gem update bundler
|
29
|
+
- rvm: rbx-3
|
30
|
+
before_install: gem update bundler
|
31
31
|
|
32
32
|
notifications:
|
33
33
|
email:
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# v0.3.0 2017-03-16
|
2
|
+
|
3
|
+
## Added
|
4
|
+
* Added `Either#either` that accepts two callbacks, runs the first if it is `Right` and the second otherwise (nkondratyev)
|
5
|
+
* Added `#fmap2` and `#fmap3` for mapping over nested structures like `List Either` and `Either Some` (flash-gordon)
|
6
|
+
* Added `Try#value_or` (dsounded)
|
7
|
+
* Added the `List` monad which acts as an immutable `Array` and plays nice with other monads. A common example is a list of `Either`s (flash-gordon)
|
8
|
+
* `#bind` made to work with keyword arguments as extra parameters to the block (flash-gordon)
|
9
|
+
* Added `List#traverse` that "flips" the list with an embedded monad (flash-gordon + damncabbage)
|
10
|
+
* Added `#tee` for all right-biased monads (flash-gordon)
|
11
|
+
|
12
|
+
[Compare v0.3.0...v0.2.1](https://github.com/dry-rb/dry-monads/compare/v0.2.1...v0.3.0)
|
13
|
+
|
14
|
+
# v0.2.1 2016-11-13
|
15
|
+
|
16
|
+
## Added
|
17
|
+
|
18
|
+
* Added `Either#tee` that is similar to `Object#tap` but executes the block only for `Right` instances (saverio-kantox)
|
19
|
+
|
20
|
+
## Fixed
|
21
|
+
|
22
|
+
* `Right(nil).to_maybe` now returns `None` with a warning instead of failing (orisaka)
|
23
|
+
* `Some#value_or` doesn't require an argument because `None#value_or` doesn't require it either if a block was passed (flash-gordon)
|
24
|
+
|
25
|
+
[Compare v0.2.1...v0.2.0](https://github.com/dry-rb/dry-monads/compare/v0.2.0...v0.2.1)
|
26
|
+
|
27
|
+
# v0.2.0 2016-09-18
|
28
|
+
|
29
|
+
## Added
|
30
|
+
|
31
|
+
* Added `Maybe#to_json` as an opt-in extension for serialization to JSON (rocknruby)
|
32
|
+
* Added `Maybe#value_or` which returns you the underlying value with a fallback in a single method call (dsounded)
|
33
|
+
|
34
|
+
[Compare v0.1.1...v0.2.0](https://github.com/dry-rb/dry-monads/compare/v0.1.1...v0.2.0)
|
35
|
+
|
36
|
+
# v0.1.1 2016-08-25
|
37
|
+
|
38
|
+
## Fixed
|
39
|
+
|
40
|
+
* Added explicit requires of `dry-equalizer`. This allows to safely load only specific monads (artofhuman)
|
41
|
+
|
42
|
+
[Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-monads/compare/v0.1.0...v0.1.1)
|
43
|
+
|
44
|
+
# v0.1.0 2016-08-23
|
45
|
+
|
46
|
+
## Added
|
47
|
+
|
48
|
+
* Support for passing extra arguments to the block in `.bind` and `.fmap` (flash-gordon)
|
49
|
+
|
50
|
+
## Changed
|
51
|
+
|
52
|
+
* Dropped MRI 2.0 support (flash-gordon)
|
53
|
+
|
54
|
+
[Compare v0.0.2...v0.1.0](https://github.com/dry-rb/dry-monads/compare/v0.0.2...v0.1.0)
|
55
|
+
|
56
|
+
# v0.0.2 2016-06-29
|
57
|
+
|
58
|
+
## Added
|
59
|
+
|
60
|
+
* Added `Either#to_either` so that you can rely on duck-typing when you work with different types of monads (timriley)
|
61
|
+
* Added `Maybe#to_maybe` for consistency with `#to_either` (flash-gordon)
|
62
|
+
|
63
|
+
[Compare v0.0.1...v0.0.2](https://github.com/dry-rb/dry-monads/compare/v0.0.1...v0.0.2)
|
64
|
+
|
65
|
+
# v0.0.1 2016-05-02
|
66
|
+
|
67
|
+
Initial release containing `Either`, `Maybe`, and `Try` monads.
|
data/Gemfile
CHANGED
data/bin/console
CHANGED
data/lib/dry/monads.rb
CHANGED
data/lib/dry/monads/either.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'dry/equalizer'
|
2
2
|
|
3
|
+
require 'dry/monads/right_biased'
|
4
|
+
require 'dry/monads/transformer'
|
5
|
+
|
3
6
|
module Dry
|
4
7
|
module Monads
|
5
8
|
# Represents a value which is either correct or an error.
|
@@ -7,19 +10,19 @@ module Dry
|
|
7
10
|
# @api public
|
8
11
|
class Either
|
9
12
|
include Dry::Equalizer(:right, :left)
|
10
|
-
|
13
|
+
include Transformer
|
11
14
|
|
12
|
-
|
13
|
-
def right?
|
14
|
-
is_a? Right
|
15
|
-
end
|
16
|
-
alias success? right?
|
15
|
+
attr_reader :right, :left
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
21
25
|
end
|
22
|
-
alias failure? left?
|
23
26
|
|
24
27
|
# Returns self, added to keep the interface compatible with other monads.
|
25
28
|
#
|
@@ -28,10 +31,20 @@ module Dry
|
|
28
31
|
self
|
29
32
|
end
|
30
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
|
+
|
31
42
|
# Represents a value that is in a correct state, i.e. everything went right.
|
32
43
|
#
|
33
44
|
# @api public
|
34
45
|
class Right < Either
|
46
|
+
include RightBiased::Right
|
47
|
+
|
35
48
|
alias value right
|
36
49
|
|
37
50
|
# @param right [Object] a value in a correct state
|
@@ -39,27 +52,24 @@ module Dry
|
|
39
52
|
@right = right
|
40
53
|
end
|
41
54
|
|
42
|
-
#
|
43
|
-
# and returns the result.
|
44
|
-
#
|
45
|
-
# If proc is nil, it expects a block to be given and will yield to it.
|
46
|
-
#
|
47
|
-
# @example
|
48
|
-
# Dry::Monads.Right(4).bind(&:succ) # => 5
|
55
|
+
# Apply the second function to value.
|
49
56
|
#
|
50
|
-
# @
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
#
|
56
|
-
def
|
57
|
-
|
58
|
-
yield(value, *args)
|
59
|
-
else
|
60
|
-
args[0].call(value, *args.drop(1))
|
61
|
-
end
|
57
|
+
# @api public
|
58
|
+
def either(_, f)
|
59
|
+
f.call(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns false
|
63
|
+
def left?
|
64
|
+
false
|
62
65
|
end
|
66
|
+
alias failure? left?
|
67
|
+
|
68
|
+
# Returns true
|
69
|
+
def right?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
alias success? right?
|
63
73
|
|
64
74
|
# Does the same thing as #bind except it also wraps the value
|
65
75
|
# in an instance of Either::Right monad. This allows for easier
|
@@ -68,33 +78,12 @@ module Dry
|
|
68
78
|
# @example
|
69
79
|
# Dry::Monads.Right(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Right(25)
|
70
80
|
#
|
71
|
-
# @param [Array<Object>]
|
81
|
+
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
72
82
|
# @return [Either::Right]
|
73
83
|
def fmap(*args, &block)
|
74
84
|
Right.new(bind(*args, &block))
|
75
85
|
end
|
76
86
|
|
77
|
-
# Does the same thing as #bind except it returns the original monad
|
78
|
-
# when the result is a Right.
|
79
|
-
#
|
80
|
-
# @example
|
81
|
-
# Dry::Monads.Right(4).tee { Right('ok') } # => Right(4)
|
82
|
-
# Dry::Monads.Right(4).tee { Left('fail') } # => Left('fail')
|
83
|
-
#
|
84
|
-
# @param [Array<Object>] args arguments will be transparently passed through to #bind
|
85
|
-
# @return [Either]
|
86
|
-
def tee(*args, &block)
|
87
|
-
bind(*args, &block).bind { self }
|
88
|
-
end
|
89
|
-
|
90
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
91
|
-
# identical to that of {Either::Left}.
|
92
|
-
#
|
93
|
-
# @return [Either::Right]
|
94
|
-
def or(*)
|
95
|
-
self
|
96
|
-
end
|
97
|
-
|
98
87
|
# @return [String]
|
99
88
|
def to_s
|
100
89
|
"Right(#{value.inspect})"
|
@@ -112,6 +101,8 @@ module Dry
|
|
112
101
|
#
|
113
102
|
# @api public
|
114
103
|
class Left < Either
|
104
|
+
include RightBiased::Left
|
105
|
+
|
115
106
|
alias value left
|
116
107
|
|
117
108
|
# @param left [Object] a value in an error state
|
@@ -119,29 +110,24 @@ module Dry
|
|
119
110
|
@left = left
|
120
111
|
end
|
121
112
|
|
122
|
-
#
|
123
|
-
# identical to that of {Either::Right}.
|
113
|
+
# Apply the first function to value.
|
124
114
|
#
|
125
|
-
# @
|
126
|
-
def
|
127
|
-
|
115
|
+
# @api public
|
116
|
+
def either(f, _)
|
117
|
+
f.call(value)
|
128
118
|
end
|
129
119
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
# @return [Either::Left]
|
134
|
-
def fmap(*)
|
135
|
-
self
|
120
|
+
# Returns true
|
121
|
+
def left?
|
122
|
+
true
|
136
123
|
end
|
124
|
+
alias failure? left?
|
137
125
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
# @return [Either::Left]
|
142
|
-
def tee(*)
|
143
|
-
self
|
126
|
+
# Returns false
|
127
|
+
def right?
|
128
|
+
false
|
144
129
|
end
|
130
|
+
alias success? right?
|
145
131
|
|
146
132
|
# If a block is given passes internal value to it and returns the result,
|
147
133
|
# otherwise simply returns the parameter val.
|
@@ -149,7 +135,7 @@ module Dry
|
|
149
135
|
# @example
|
150
136
|
# Dry::Monads.Left(ArgumentError.new('error message')).or(&:message) # => "error message"
|
151
137
|
#
|
152
|
-
# @param [Array<Object>]
|
138
|
+
# @param args [Array<Object>] arguments that will be passed to a block
|
153
139
|
# if one was given, otherwise the first
|
154
140
|
# value will be returned
|
155
141
|
# @return [Object]
|
@@ -161,6 +147,18 @@ module Dry
|
|
161
147
|
end
|
162
148
|
end
|
163
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
|
+
|
164
162
|
# @return [String]
|
165
163
|
def to_s
|
166
164
|
"Left(#{value.inspect})"
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require 'dry/equalizer'
|
2
|
+
require 'dry/monads/maybe'
|
3
|
+
require 'dry/monads/transformer'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Monads
|
7
|
+
class List
|
8
|
+
class << self
|
9
|
+
# Builds a list.
|
10
|
+
#
|
11
|
+
# @param values [Array<Object>] List elements
|
12
|
+
# @return [List]
|
13
|
+
def [](*values)
|
14
|
+
new(values)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Coerces a value to a list. `nil` will be coerced to an empty list.
|
18
|
+
#
|
19
|
+
# @param value [Object] Value
|
20
|
+
# @param type [Monad] Embedded monad type (used in case of list of monadic values)
|
21
|
+
# @return [List]
|
22
|
+
def coerce(value, type = nil)
|
23
|
+
if value.nil?
|
24
|
+
List.new([], type)
|
25
|
+
elsif value.respond_to?(:to_ary)
|
26
|
+
List.new(value.to_ary, type)
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Can't coerce #{value.inspect} to List"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Wraps a value with a list.
|
33
|
+
#
|
34
|
+
# @param value [Object] any object
|
35
|
+
# @return [List]
|
36
|
+
def pure(value, type = nil)
|
37
|
+
new([value], type)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
include Dry::Equalizer(:value, :type)
|
42
|
+
include Transformer
|
43
|
+
|
44
|
+
# Internal array value
|
45
|
+
attr_reader :value, :type
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def initialize(value, type = nil)
|
49
|
+
@value = value
|
50
|
+
@type = type
|
51
|
+
end
|
52
|
+
|
53
|
+
# Lifts a block/proc and runs it against each member of the list.
|
54
|
+
# The block must return a value coercible to a list.
|
55
|
+
# As in other monads if no block given the first argument will
|
56
|
+
# be treated as callable and used instead.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# Dry::Monads::List[1, 2].bind { |x| [x + 1] } # => List[2, 3]
|
60
|
+
# Dry::Monads::List[1, 2].bind(-> x { [x, x + 1] }) # => List[1, 2, 2, 3]
|
61
|
+
#
|
62
|
+
# @param args [Array<Object>] arguments will be passed to the block or proc
|
63
|
+
# @return [List]
|
64
|
+
def bind(*args)
|
65
|
+
if block_given?
|
66
|
+
List.coerce(value.map { |v| yield(v, *args) }.reduce([], &:+))
|
67
|
+
else
|
68
|
+
obj, *rest = args
|
69
|
+
List.coerce(value.map { |v| obj.(v, *rest) }.reduce([], &:+))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps a block over the list. Acts as `Array#map`.
|
74
|
+
# As in other monads if no block given the first argument will
|
75
|
+
# be treated as callable and used instead.
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# Dry::Monads::List[1, 2].fmap { |x| x + 1 } # => List[2, 3]
|
79
|
+
#
|
80
|
+
# @param args [Array<Object>] arguments will be passed to the block or proc
|
81
|
+
# @return [List]
|
82
|
+
def fmap(*args)
|
83
|
+
if block_given?
|
84
|
+
List.new(value.map { |v| yield(v, *args) })
|
85
|
+
else
|
86
|
+
obj, *rest = args
|
87
|
+
List.new(value.map { |v| obj.(v, *rest) })
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Maps a block over the list. Acts as `Array#map`.
|
92
|
+
# Requires a block.
|
93
|
+
#
|
94
|
+
# @return [List]
|
95
|
+
def map(&block)
|
96
|
+
if block
|
97
|
+
fmap(block)
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Missing block"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Concatenates two lists.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# Dry::Monads::List[1, 2] + Dry::Monads::List[3, 4] # => List[1, 2, 3, 4]
|
107
|
+
#
|
108
|
+
# @param other [List] Other list
|
109
|
+
# @return [List]
|
110
|
+
def +(other)
|
111
|
+
List.new(to_ary + other.to_ary)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns a string representation of the list.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# Dry::Monads::List[1, 2, 3].inspect # => "List[1, 2, 3]"
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
def inspect
|
121
|
+
"List#{ value.inspect }"
|
122
|
+
end
|
123
|
+
alias_method :to_s, :inspect
|
124
|
+
|
125
|
+
# Coerces to an array
|
126
|
+
alias_method :to_ary, :value
|
127
|
+
alias_method :to_a, :to_ary
|
128
|
+
|
129
|
+
# Returns the first element.
|
130
|
+
#
|
131
|
+
# @return [Object]
|
132
|
+
def first
|
133
|
+
value.first
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the last element.
|
137
|
+
#
|
138
|
+
# @return [Object]
|
139
|
+
def last
|
140
|
+
value.last
|
141
|
+
end
|
142
|
+
|
143
|
+
# Folds the list from the left.
|
144
|
+
#
|
145
|
+
# @param initial [Object] Initial value
|
146
|
+
# @return [Object]
|
147
|
+
def fold_left(initial)
|
148
|
+
value.reduce(initial) { |acc, v| yield(acc, v) }
|
149
|
+
end
|
150
|
+
alias_method :foldl, :fold_left
|
151
|
+
alias_method :reduce, :fold_left
|
152
|
+
|
153
|
+
# Folds the list from the right.
|
154
|
+
#
|
155
|
+
# @param initial [Object] Initial value
|
156
|
+
# @return [Object]
|
157
|
+
def fold_right(initial)
|
158
|
+
value.reverse.reduce(initial) { |a, b| yield(b, a) }
|
159
|
+
end
|
160
|
+
alias_method :foldr, :fold_right
|
161
|
+
|
162
|
+
# Whether the list is empty.
|
163
|
+
#
|
164
|
+
# @return [TrueClass, FalseClass]
|
165
|
+
def empty?
|
166
|
+
value.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
# Sorts the list.
|
170
|
+
#
|
171
|
+
# @return [List]
|
172
|
+
def sort
|
173
|
+
coerce(value.sort)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Filters elements with a block
|
177
|
+
#
|
178
|
+
# @return [List]
|
179
|
+
def filter
|
180
|
+
coerce(value.select { |e| yield(e) })
|
181
|
+
end
|
182
|
+
alias_method :select, :filter
|
183
|
+
|
184
|
+
# List size.
|
185
|
+
#
|
186
|
+
# @return [Integer]
|
187
|
+
def size
|
188
|
+
value.size
|
189
|
+
end
|
190
|
+
|
191
|
+
# Reverses the list.
|
192
|
+
#
|
193
|
+
# @return [List]
|
194
|
+
def reverse
|
195
|
+
coerce(value.reverse)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns the first element wrapped with a `Maybe`.
|
199
|
+
#
|
200
|
+
# @return [Maybe<Object>]
|
201
|
+
def head
|
202
|
+
Maybe.coerce(value.first)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns list's tail.
|
206
|
+
#
|
207
|
+
# @return [List]
|
208
|
+
def tail
|
209
|
+
coerce(value.drop(1))
|
210
|
+
end
|
211
|
+
|
212
|
+
# Turns the list into a types one.
|
213
|
+
# Type is required for some operations like .traverse.
|
214
|
+
#
|
215
|
+
# @param type [Monad] Monad instance
|
216
|
+
# @return [List] Typed list
|
217
|
+
def typed(type = nil)
|
218
|
+
if type.nil?
|
219
|
+
if size.zero?
|
220
|
+
raise ArgumentError, "Cannot infer monad for an empty list"
|
221
|
+
else
|
222
|
+
self.class.new(value, value[0].monad)
|
223
|
+
end
|
224
|
+
else
|
225
|
+
self.class.new(value, type)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Whether the list is types
|
230
|
+
#
|
231
|
+
# @return [Boolean]
|
232
|
+
def typed?
|
233
|
+
!type.nil?
|
234
|
+
end
|
235
|
+
|
236
|
+
# Traverses the list with a block (or without it).
|
237
|
+
# This methods "flips" List structure with the given monad (obtained from the type).
|
238
|
+
# Note that traversing requires the list to be types.
|
239
|
+
# Also if a block given, its returning type must be equal list's type.
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# List<Either>[Right(1), Right(2)].traverse # => Right([1, 2])
|
243
|
+
# List<Maybe>[Some(1), None, Some(3)].traverse # => None
|
244
|
+
#
|
245
|
+
# @return [Monad] Result is a monadic value
|
246
|
+
def traverse
|
247
|
+
unless typed?
|
248
|
+
raise StandardError, "Cannot traverse an untyped list"
|
249
|
+
end
|
250
|
+
|
251
|
+
foldl(type.pure(EMPTY)) { |acc, el|
|
252
|
+
acc.bind { |unwrapped|
|
253
|
+
mapped = block_given? ? yield(el) : el
|
254
|
+
mapped.fmap { |i| unwrapped + List[i] }
|
255
|
+
}
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns the List monad.
|
260
|
+
#
|
261
|
+
# @return [Monad]
|
262
|
+
def monad
|
263
|
+
List
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
def coerce(other)
|
269
|
+
self.class.coerce(other)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Empty list
|
273
|
+
EMPTY = List.new([].freeze).freeze
|
274
|
+
|
275
|
+
module Mixin
|
276
|
+
List = List
|
277
|
+
L = List
|
278
|
+
|
279
|
+
def List(value)
|
280
|
+
List.coerce(value)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
data/lib/dry/monads/maybe.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'dry/equalizer'
|
2
2
|
|
3
|
+
require 'dry/monads/right_biased'
|
4
|
+
require 'dry/monads/transformer'
|
5
|
+
|
3
6
|
module Dry
|
4
7
|
module Monads
|
5
8
|
# Represents a value which can exist or not, i.e. it could be nil.
|
@@ -7,15 +10,27 @@ module Dry
|
|
7
10
|
# @api public
|
8
11
|
class Maybe
|
9
12
|
include Dry::Equalizer(:value)
|
13
|
+
include Transformer
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
class << self
|
16
|
+
# Wraps the given value with into a Maybe object.
|
17
|
+
#
|
18
|
+
# @param value [Object] the value to be stored in the monad
|
19
|
+
# @return [Maybe::Some, Maybe::None]
|
20
|
+
def coerce(value)
|
21
|
+
if value.nil?
|
22
|
+
None.instance
|
23
|
+
else
|
24
|
+
Some.new(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias_method :lift, :coerce
|
28
|
+
|
29
|
+
# Wraps the given value with `Some`.
|
30
|
+
#
|
31
|
+
# @param value [Object] the value to be stored inside Some
|
32
|
+
# @return [Maybe::Some]
|
33
|
+
def pure(value)
|
19
34
|
Some.new(value)
|
20
35
|
end
|
21
36
|
end
|
@@ -37,10 +52,20 @@ module Dry
|
|
37
52
|
self
|
38
53
|
end
|
39
54
|
|
55
|
+
# Returns the Maybe monad.
|
56
|
+
# This is how we're doing polymorphism in Ruby 😕
|
57
|
+
#
|
58
|
+
# @return [Monad]
|
59
|
+
def monad
|
60
|
+
Maybe
|
61
|
+
end
|
62
|
+
|
40
63
|
# Represents a value that is present, i.e. not nil.
|
41
64
|
#
|
42
65
|
# @api public
|
43
66
|
class Some < Maybe
|
67
|
+
include RightBiased::Right
|
68
|
+
|
44
69
|
attr_reader :value
|
45
70
|
|
46
71
|
def initialize(value)
|
@@ -48,28 +73,6 @@ module Dry
|
|
48
73
|
@value = value
|
49
74
|
end
|
50
75
|
|
51
|
-
# Calls the passed in Proc object with value stored in self
|
52
|
-
# and returns the result.
|
53
|
-
#
|
54
|
-
# If proc is nil, it expects a block to be given and will yield to it.
|
55
|
-
#
|
56
|
-
# @example
|
57
|
-
# Dry::Monads.Some(4).bind(&:succ) # => 5
|
58
|
-
#
|
59
|
-
# @param [Array<Object>] args arguments that will be passed to a block
|
60
|
-
# if one was given, otherwise the first
|
61
|
-
# value assumed to be a Proc (callable)
|
62
|
-
# object and the rest of args will be passed
|
63
|
-
# to this object along with the internal value
|
64
|
-
# @return [Object] result of calling proc or block on the internal value
|
65
|
-
def bind(*args)
|
66
|
-
if block_given?
|
67
|
-
yield(value, *args)
|
68
|
-
else
|
69
|
-
args[0].call(value, *args.drop(1))
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
76
|
# Does the same thing as #bind except it also wraps the value
|
74
77
|
# in an instance of Maybe::Some monad. This allows for easier
|
75
78
|
# chaining of calls.
|
@@ -77,28 +80,13 @@ module Dry
|
|
77
80
|
# @example
|
78
81
|
# Dry::Monads.Some(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Some(25)
|
79
82
|
#
|
80
|
-
# @param [Array<Object>]
|
83
|
+
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
81
84
|
# @return [Maybe::Some, Maybe::None] Lifted result, i.e. nil will be mapped to None,
|
82
85
|
# other values will be wrapped with Some
|
83
86
|
def fmap(*args, &block)
|
84
87
|
self.class.lift(bind(*args, &block))
|
85
88
|
end
|
86
89
|
|
87
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
88
|
-
# identical to that of {Maybe::None}.
|
89
|
-
#
|
90
|
-
# @return [Maybe::Some]
|
91
|
-
def or(*)
|
92
|
-
self
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns value. It exists to keep the interface identical to that of {Maybe::None}.
|
96
|
-
#
|
97
|
-
# @return [Object]
|
98
|
-
def value_or(_val = nil)
|
99
|
-
value
|
100
|
-
end
|
101
|
-
|
102
90
|
# @return [String]
|
103
91
|
def to_s
|
104
92
|
"Some(#{value.inspect})"
|
@@ -110,6 +98,8 @@ module Dry
|
|
110
98
|
#
|
111
99
|
# @api public
|
112
100
|
class None < Maybe
|
101
|
+
include RightBiased::Left
|
102
|
+
|
113
103
|
@instance = new
|
114
104
|
singleton_class.send(:attr_reader, :instance)
|
115
105
|
|
@@ -118,22 +108,6 @@ module Dry
|
|
118
108
|
nil
|
119
109
|
end
|
120
110
|
|
121
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
122
|
-
# identical to that of {Maybe::Some}.
|
123
|
-
#
|
124
|
-
# @return [Maybe::None]
|
125
|
-
def bind(*)
|
126
|
-
self
|
127
|
-
end
|
128
|
-
|
129
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
130
|
-
# identical to that of {Maybe::Some}.
|
131
|
-
#
|
132
|
-
# @return [Maybe::None]
|
133
|
-
def fmap(*)
|
134
|
-
self
|
135
|
-
end
|
136
|
-
|
137
111
|
# If a block is given passes internal value to it and returns the result,
|
138
112
|
# otherwise simply returns the parameter val.
|
139
113
|
#
|
@@ -141,7 +115,8 @@ module Dry
|
|
141
115
|
# Dry::Monads.None.or('no value') # => "no value"
|
142
116
|
# Dry::Monads.None.or { Time.now } # => current time
|
143
117
|
#
|
144
|
-
# @param
|
118
|
+
# @param args [Array<Object>] if no block given the first argument will be returned
|
119
|
+
# otherwise arguments will be transparently passed to the block
|
145
120
|
# @return [Object]
|
146
121
|
def or(*args)
|
147
122
|
if block_given?
|
@@ -151,15 +126,18 @@ module Dry
|
|
151
126
|
end
|
152
127
|
end
|
153
128
|
|
154
|
-
#
|
129
|
+
# A lifted version of `#or`. Applies `Maybe.lift` to the passed value or
|
130
|
+
# to the block result.
|
155
131
|
#
|
156
|
-
# @
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
132
|
+
# @example
|
133
|
+
# Dry::Monads.None.or_fmap('no value') # => Some("no value")
|
134
|
+
# Dry::Monads.None.or_fmap { Time.now } # => Some(current time)
|
135
|
+
#
|
136
|
+
# @param args [Array<Object>] arguments will be passed to the underlying `#or` call
|
137
|
+
# @return [Maybe::Some, Maybe::None] Lifted `#or` result, i.e. nil will be mapped to None,
|
138
|
+
# other values will be wrapped with Some
|
139
|
+
def or_fmap(*args, &block)
|
140
|
+
Maybe.lift(self.or(*args, &block))
|
163
141
|
end
|
164
142
|
|
165
143
|
# @return [String]
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Dry
|
2
|
+
module Monads
|
3
|
+
module RightBiased
|
4
|
+
module Right
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
# Calls the passed in Proc object with value stored in self
|
8
|
+
# and returns the result.
|
9
|
+
#
|
10
|
+
# If proc is nil, it expects a block to be given and will yield to it.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Dry::Monads.Right(4).bind(&:succ) # => 5
|
14
|
+
#
|
15
|
+
# @param [Array<Object>] args arguments that will be passed to a block
|
16
|
+
# if one was given, otherwise the first
|
17
|
+
# value assumed to be a Proc (callable)
|
18
|
+
# object and the rest of args will be passed
|
19
|
+
# to this object along with the internal value
|
20
|
+
# @return [Object] result of calling proc or block on the internal value
|
21
|
+
def bind(*args, **kwargs)
|
22
|
+
vargs, vkwargs = destructure(value)
|
23
|
+
kw = kwargs.empty? && vkwargs.empty? ? [] : [kwargs.merge(vkwargs)]
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
yield(*vargs, *args, *kw)
|
27
|
+
else
|
28
|
+
obj, *rest = args
|
29
|
+
obj.call(*vargs, *rest, *kw)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Does the same thing as #bind except it returns the original monad
|
34
|
+
# when the result is a Right.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Dry::Monads.Right(4).tee { Right('ok') } # => Right(4)
|
38
|
+
# Dry::Monads.Right(4).tee { Left('fail') } # => Left('fail')
|
39
|
+
#
|
40
|
+
# @param [Array<Object>] args arguments will be transparently passed through to #bind
|
41
|
+
# @return [RightBiased::Right]
|
42
|
+
def tee(*args, &block)
|
43
|
+
bind(*args, &block).bind { self }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Abstract method for lifting a block over the monad type
|
47
|
+
# Must be implemented for a right-biased monad
|
48
|
+
#
|
49
|
+
# @return [RightBiased::Right]
|
50
|
+
def fmap(*)
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
# Ignores arguments and returns self. It exists to keep the interface
|
55
|
+
# identical to that of {RightBiased::Left}.
|
56
|
+
#
|
57
|
+
# @return [RightBiased::Right]
|
58
|
+
def or(*)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# A lifted version of `#or`. For {RightBiased::Right} acts in the same way as `#or`,
|
63
|
+
# that is returns itselt.
|
64
|
+
#
|
65
|
+
# @return [RightBiased::Right]
|
66
|
+
def or_fmap(*)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns value. It exists to keep the interface identical to that of RightBiased::Left
|
71
|
+
#
|
72
|
+
# @return [Object]
|
73
|
+
def value_or(_val = nil)
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
def destructure(*args, **kwargs)
|
81
|
+
[args, kwargs]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Left
|
86
|
+
attr_reader :value
|
87
|
+
|
88
|
+
# Ignores the input parameter and returns self. It exists to keep the interface
|
89
|
+
# identical to that of {RightBiased::Right}.
|
90
|
+
#
|
91
|
+
# @return [RightBiased::Left]
|
92
|
+
def bind(*)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Ignores the input parameter and returns self. It exists to keep the interface
|
97
|
+
# identical to that of {RightBiased::Right}.
|
98
|
+
#
|
99
|
+
# @return [RightBiased::Left]
|
100
|
+
def tee(*)
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# Ignores the input parameter and returns self. It exists to keep the interface
|
105
|
+
# identical to that of {RightBiased::Right}.
|
106
|
+
#
|
107
|
+
# @return [RightBiased::Left]
|
108
|
+
def fmap(*)
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Left-biased #bind version.
|
113
|
+
#
|
114
|
+
# @example
|
115
|
+
# Dry::Monads.Left(ArgumentError.new('error message')).or(&:message) # => "error message"
|
116
|
+
# Dry::Monads.None.or('no value') # => "no value"
|
117
|
+
# Dry::Monads.None.or { Time.now } # => current time
|
118
|
+
#
|
119
|
+
# @return [Object]
|
120
|
+
def or(*)
|
121
|
+
raise NotImplementedError
|
122
|
+
end
|
123
|
+
|
124
|
+
# A lifted version of `#or`. This is basically `#or` + `#fmap`.
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# Dry::Monads.None.or('no value') # => Some("no value")
|
128
|
+
# Dry::Monads.None.or { Time.now } # => Some(current time)
|
129
|
+
#
|
130
|
+
# @return [RightBiased::Left, RightBiased::Right]
|
131
|
+
def or_fmap(*)
|
132
|
+
raise NotImplementedError
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the passed value
|
136
|
+
#
|
137
|
+
# @returns [Object]
|
138
|
+
def value_or(val = nil)
|
139
|
+
if block_given?
|
140
|
+
yield
|
141
|
+
else
|
142
|
+
val
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Dry
|
2
|
+
module Monads
|
3
|
+
module Transformer
|
4
|
+
# Lifts a block/proc over the 2-level nested structure.
|
5
|
+
# This is essentially fmap . fmap (. is the function composition
|
6
|
+
# operator from Haskell) or the functor instance for
|
7
|
+
# a two-level monadic structure like List Either.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# List[Right(1), Left(1)].fmap2 { |x| x + 1 } # => List[Right(2), Left(1)]
|
11
|
+
# Right(None).fmap2 { |x| x + 1 } # => Right(None)
|
12
|
+
#
|
13
|
+
# @param args [Array<Object>] arguments will be passed to the block or the proc
|
14
|
+
# @return [Object] some monadic value
|
15
|
+
def fmap2(*args)
|
16
|
+
if block_given?
|
17
|
+
fmap { |a| a.fmap { |b| yield(b, *args) } }
|
18
|
+
else
|
19
|
+
func, *rest = args
|
20
|
+
fmap { |a| a.fmap { |b| func.(b, *rest) } }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Lifts a block/proc over the 3-level nested structure.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# List[Right(Some(1)), Left(Some(1))].fmap3 { |x| x + 1 } # => List[Right(Some(2)), Left(Some(1))]
|
28
|
+
# Right(None).fmap3 { |x| x + 1 } # => Right(None)
|
29
|
+
#
|
30
|
+
# @param args [Array<Object>] arguments will be passed to the block or the proc
|
31
|
+
# @return [Object] some monadic value
|
32
|
+
def fmap3(*args)
|
33
|
+
if block_given?
|
34
|
+
fmap { |a| a.fmap { |b| b.fmap { |c| yield(c, *args) } } }
|
35
|
+
else
|
36
|
+
func, *rest = args
|
37
|
+
fmap { |a| a.fmap { |b| b.fmap { |c| func.(c, *rest) } } }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/dry/monads/try.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'dry/equalizer'
|
2
2
|
|
3
|
+
require 'dry/monads/right_biased'
|
4
|
+
|
3
5
|
module Dry
|
4
6
|
module Monads
|
5
7
|
# Represents a value which can be either success or a failure (an exception).
|
@@ -7,7 +9,7 @@ module Dry
|
|
7
9
|
#
|
8
10
|
# @api public
|
9
11
|
class Try
|
10
|
-
attr_reader :exception
|
12
|
+
attr_reader :exception
|
11
13
|
|
12
14
|
# Calls the passed in proc object and if successful stores the result in a
|
13
15
|
# {Try::Success} monad, but if one of the specified exceptions was raised it stores
|
@@ -37,6 +39,13 @@ module Dry
|
|
37
39
|
# @api public
|
38
40
|
class Success < Try
|
39
41
|
include Dry::Equalizer(:value, :catchable)
|
42
|
+
include RightBiased::Right
|
43
|
+
|
44
|
+
# Using #or is not a good practice, you should process exceptions
|
45
|
+
# explicitly hence we don't offer an easy way to ignore them.
|
46
|
+
# Use Try#to_maybe if you're sure you need `#or` for `Try`.
|
47
|
+
undef :or
|
48
|
+
|
40
49
|
attr_reader :catchable
|
41
50
|
|
42
51
|
# @param exceptions [Array<Exception>] list of exceptions to be rescued
|
@@ -46,6 +55,9 @@ module Dry
|
|
46
55
|
@value = value
|
47
56
|
end
|
48
57
|
|
58
|
+
alias bind_call bind
|
59
|
+
private :bind_call
|
60
|
+
|
49
61
|
# Calls the passed in Proc object with value stored in self
|
50
62
|
# and returns the result.
|
51
63
|
#
|
@@ -56,18 +68,14 @@ module Dry
|
|
56
68
|
# success.bind(->(n) { n / 2 }) # => 5
|
57
69
|
# success.bind { |n| n / 0 } # => Try::Failure(ZeroDivisionError: divided by 0)
|
58
70
|
#
|
59
|
-
# @param [Array<Object>]
|
71
|
+
# @param args [Array<Object>] arguments that will be passed to a block
|
60
72
|
# if one was given, otherwise the first
|
61
73
|
# value assumed to be a Proc (callable)
|
62
74
|
# object and the rest of args will be passed
|
63
75
|
# to this object along with the internal value
|
64
76
|
# @return [Object, Try::Failure]
|
65
|
-
def bind(*
|
66
|
-
|
67
|
-
yield(value, *args)
|
68
|
-
else
|
69
|
-
args[0].call(value, *args.drop(1))
|
70
|
-
end
|
77
|
+
def bind(*)
|
78
|
+
super
|
71
79
|
rescue *catchable => e
|
72
80
|
Failure.new(e)
|
73
81
|
end
|
@@ -81,15 +89,13 @@ module Dry
|
|
81
89
|
# success.fmap(&:succ).fmap(&:succ).value # => 12
|
82
90
|
# success.fmap(&:succ).fmap { |n| n / 0 }.fmap(&:succ).value # => nil
|
83
91
|
#
|
84
|
-
# @param [Array<Object>]
|
92
|
+
# @param args [Array<Object>] extra arguments for the block, arguments are being processes
|
85
93
|
# just as in #bind
|
86
94
|
# @return [Try::Success, Try::Failure]
|
87
95
|
def fmap(*args, &block)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
Try.lift(catchable, -> { args[0].call(value, *args.drop(1)) })
|
92
|
-
end
|
96
|
+
Success.new(catchable, bind_call(*args, &block))
|
97
|
+
rescue *catchable => e
|
98
|
+
Failure.new(e)
|
93
99
|
end
|
94
100
|
|
95
101
|
# @return [Maybe]
|
@@ -114,28 +120,14 @@ module Dry
|
|
114
120
|
# @api public
|
115
121
|
class Failure < Try
|
116
122
|
include Dry::Equalizer(:exception)
|
123
|
+
include RightBiased::Left
|
124
|
+
undef :or
|
117
125
|
|
118
126
|
# @param exception [Exception]
|
119
127
|
def initialize(exception)
|
120
128
|
@exception = exception
|
121
129
|
end
|
122
130
|
|
123
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
124
|
-
# identical to that of {Try::Success}.
|
125
|
-
#
|
126
|
-
# @return [Try::Failure]
|
127
|
-
def bind(*)
|
128
|
-
self
|
129
|
-
end
|
130
|
-
|
131
|
-
# Ignores arguments and returns self. It exists to keep the interface
|
132
|
-
# identical to that of {Try::Success}.
|
133
|
-
#
|
134
|
-
# @return [Try::Failure]
|
135
|
-
def fmap(*)
|
136
|
-
self
|
137
|
-
end
|
138
|
-
|
139
131
|
# @return [Maybe::None]
|
140
132
|
def to_maybe
|
141
133
|
Dry::Monads::None()
|
@@ -171,12 +163,14 @@ module Dry
|
|
171
163
|
module Mixin
|
172
164
|
Try = Try
|
173
165
|
|
166
|
+
DEFAULT_EXCEPTIONS = [StandardError].freeze
|
167
|
+
|
174
168
|
# A convenience wrapper for {Try.lift}.
|
175
169
|
# If no exceptions are provided it falls back to StandardError.
|
176
170
|
# In general, relying on this behaviour is not recommended as it can lead to unnoticed
|
177
171
|
# bugs and it is always better to explicitly specify a list of exceptions if possible.
|
178
172
|
def Try(*exceptions, &f)
|
179
|
-
catchable = exceptions.
|
173
|
+
catchable = exceptions.empty? ? DEFAULT_EXCEPTIONS : exceptions.flatten
|
180
174
|
Try.lift(catchable, f)
|
181
175
|
end
|
182
176
|
end
|
data/lib/dry/monads/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-monads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Shilnikov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|
@@ -76,9 +76,8 @@ files:
|
|
76
76
|
- ".codeclimate.yml"
|
77
77
|
- ".gitignore"
|
78
78
|
- ".rspec"
|
79
|
-
- ".rubocop.yml"
|
80
|
-
- ".rubocop_todo.yml"
|
81
79
|
- ".travis.yml"
|
80
|
+
- CHANGELOG.md
|
82
81
|
- Gemfile
|
83
82
|
- LICENSE
|
84
83
|
- README.md
|
@@ -89,7 +88,10 @@ files:
|
|
89
88
|
- lib/dry-monads.rb
|
90
89
|
- lib/dry/monads.rb
|
91
90
|
- lib/dry/monads/either.rb
|
91
|
+
- lib/dry/monads/list.rb
|
92
92
|
- lib/dry/monads/maybe.rb
|
93
|
+
- lib/dry/monads/right_biased.rb
|
94
|
+
- lib/dry/monads/transformer.rb
|
93
95
|
- lib/dry/monads/try.rb
|
94
96
|
- lib/dry/monads/version.rb
|
95
97
|
- lib/json/add/dry/monads/maybe.rb
|
@@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
116
|
version: '0'
|
115
117
|
requirements: []
|
116
118
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
119
|
+
rubygems_version: 2.6.10
|
118
120
|
signing_key:
|
119
121
|
specification_version: 4
|
120
122
|
summary: Common monads for Ruby.
|
data/.rubocop.yml
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
|
-
|
3
|
-
AllCops:
|
4
|
-
Exclude:
|
5
|
-
- 'spec/integration/either_spec.rb'
|
6
|
-
- 'vendor/**/*'
|
7
|
-
|
8
|
-
Style/Documentation:
|
9
|
-
Enabled: false
|
10
|
-
|
11
|
-
Style/StringLiterals:
|
12
|
-
Enabled: false
|
13
|
-
|
14
|
-
Style/MethodName:
|
15
|
-
Enabled: false
|
16
|
-
|
17
|
-
Style/LambdaCall:
|
18
|
-
Enabled: false
|
19
|
-
|
20
|
-
Style/StabbyLambdaParentheses:
|
21
|
-
Enabled: false
|
22
|
-
|
23
|
-
Style/NestedParenthesizedCalls:
|
24
|
-
Enabled: false
|
25
|
-
|
26
|
-
Style/MultilineBlockChain:
|
27
|
-
Enabled: false
|
28
|
-
|
29
|
-
Style/GuardClause:
|
30
|
-
Enabled: false
|
31
|
-
|
32
|
-
Style/SymbolProc:
|
33
|
-
Exclude:
|
34
|
-
# This rule is broken in specs on purpose to make examples clearer
|
35
|
-
- 'spec/**/*'
|
36
|
-
|
37
|
-
Style/SignalException:
|
38
|
-
Exclude:
|
39
|
-
- 'spec/**/*'
|
40
|
-
|
41
|
-
Style/ModuleFunction:
|
42
|
-
Enabled: false
|
43
|
-
|
44
|
-
Style/RescueModifier:
|
45
|
-
Enabled: false
|
46
|
-
|
47
|
-
Metrics/LineLength:
|
48
|
-
Max: 110
|
49
|
-
|
50
|
-
Style/FileName:
|
51
|
-
Exclude:
|
52
|
-
- 'lib/dry-monads.rb'
|
53
|
-
|
54
|
-
Lint/Debugger:
|
55
|
-
Exclude:
|
56
|
-
- 'bin/console'
|
57
|
-
|
58
|
-
Lint/HandleExceptions:
|
59
|
-
Exclude:
|
60
|
-
- 'spec/spec_helper.rb'
|
61
|
-
|
62
|
-
Security/JSONLoad:
|
63
|
-
Exclude:
|
64
|
-
- 'spec/**/*'
|
data/.rubocop_todo.yml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
# This configuration was generated by
|
2
|
-
# `rubocop --auto-gen-config`
|
3
|
-
# on 2016-04-29 01:33:48 +0100 using RuboCop version 0.39.0.
|
4
|
-
# The point is for the user to remove these configuration records
|
5
|
-
# one by one as the offenses are removed from the code base.
|
6
|
-
# Note that changes in the inspected code, or installation of new
|
7
|
-
# versions of RuboCop, may require this file to be generated again.
|