dry-monads 1.3.2 → 1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -73
  3. data/LICENSE +1 -1
  4. data/README.md +18 -38
  5. data/dry-monads.gemspec +32 -30
  6. data/lib/dry-monads.rb +3 -1
  7. data/lib/dry/monads.rb +4 -2
  8. data/lib/dry/monads/all.rb +4 -2
  9. data/lib/dry/monads/constants.rb +1 -1
  10. data/lib/dry/monads/conversion_stubs.rb +2 -0
  11. data/lib/dry/monads/curry.rb +2 -0
  12. data/lib/dry/monads/do.rb +55 -17
  13. data/lib/dry/monads/do/all.rb +39 -17
  14. data/lib/dry/monads/do/mixin.rb +2 -0
  15. data/lib/dry/monads/either.rb +9 -7
  16. data/lib/dry/monads/errors.rb +8 -3
  17. data/lib/dry/monads/lazy.rb +19 -6
  18. data/lib/dry/monads/list.rb +31 -30
  19. data/lib/dry/monads/maybe.rb +90 -19
  20. data/lib/dry/monads/registry.rb +15 -12
  21. data/lib/dry/monads/result.rb +42 -15
  22. data/lib/dry/monads/result/fixed.rb +35 -24
  23. data/lib/dry/monads/right_biased.rb +45 -24
  24. data/lib/dry/monads/task.rb +25 -22
  25. data/lib/dry/monads/transformer.rb +4 -1
  26. data/lib/dry/monads/traverse.rb +9 -1
  27. data/lib/dry/monads/try.rb +51 -13
  28. data/lib/dry/monads/unit.rb +6 -2
  29. data/lib/dry/monads/validated.rb +27 -20
  30. data/lib/dry/monads/version.rb +3 -1
  31. data/lib/json/add/dry/monads/maybe.rb +4 -3
  32. metadata +27 -75
  33. data/.codeclimate.yml +0 -12
  34. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  35. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  36. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  37. data/.github/workflows/ci.yml +0 -74
  38. data/.github/workflows/docsite.yml +0 -34
  39. data/.github/workflows/sync_configs.yml +0 -34
  40. data/.gitignore +0 -10
  41. data/.rspec +0 -4
  42. data/.rubocop.yml +0 -89
  43. data/.yardopts +0 -4
  44. data/CODE_OF_CONDUCT.md +0 -13
  45. data/CONTRIBUTING.md +0 -29
  46. data/Gemfile +0 -23
  47. data/Rakefile +0 -6
  48. data/bin/console +0 -16
  49. data/bin/setup +0 -7
  50. data/docsite/source/case-equality.html.md +0 -42
  51. data/docsite/source/do-notation.html.md +0 -207
  52. data/docsite/source/getting-started.html.md +0 -142
  53. data/docsite/source/index.html.md +0 -179
  54. data/docsite/source/list.html.md +0 -87
  55. data/docsite/source/maybe.html.md +0 -146
  56. data/docsite/source/pattern-matching.html.md +0 -68
  57. data/docsite/source/result.html.md +0 -190
  58. data/docsite/source/task.html.md +0 -126
  59. data/docsite/source/tracing-failures.html.md +0 -32
  60. data/docsite/source/try.html.md +0 -76
  61. data/docsite/source/unit.html.md +0 -36
  62. data/docsite/source/validated.html.md +0 -88
  63. data/log/.gitkeep +0 -0
@@ -1,87 +0,0 @@
1
- ---
2
- title: List
3
- layout: gem-single
4
- name: dry-monads
5
- ---
6
-
7
- ### `bind`
8
-
9
- Lifts a block/proc and runs it against each member of the list. The block must return a value coercible to a list. As in other monads if no block given the first argument will be treated as callable and used instead.
10
-
11
- ```ruby
12
- require 'dry/monads/list'
13
-
14
- M = Dry::Monads
15
-
16
- M::List[1, 2].bind { |x| [x + 1] } # => List[2, 3]
17
- M::List[1, 2].bind(-> x { [x, x + 1] }) # => List[1, 2, 2, 3]
18
-
19
- M::List[1, nil].bind { |x| [x + 1] } # => error
20
- ```
21
-
22
- ### `fmap`
23
-
24
- Maps a block over the list. Acts as `Array#map`. As in other monads, if no block given the first argument will be treated as callable and used instead.
25
-
26
- ```ruby
27
- require 'dry/monads/list'
28
-
29
- M = Dry::Monads
30
-
31
- M::List[1, 2].fmap { |x| x + 1 } # => List[2, 3]
32
- ```
33
-
34
- ### `value`
35
-
36
- You always can unwrap the result by calling `value`.
37
-
38
- ```ruby
39
- require 'dry/monads/list'
40
-
41
- M = Dry::Monads
42
-
43
- M::List[1, 2].value # => [1, 2]
44
- ```
45
-
46
- ### Concatenation
47
-
48
- ```ruby
49
- require 'dry/monads/list'
50
-
51
- M = Dry::Monads
52
-
53
- M::List[1, 2] + M::List[3, 4] # => List[1, 2, 3, 4]
54
- ```
55
-
56
- ### `head` and `tail`
57
-
58
- `head` returns the first element wrapped with a `Maybe`.
59
-
60
- ```ruby
61
- require 'dry/monads/list'
62
-
63
- M = Dry::Monads
64
-
65
- M::List[1, 2, 3, 4].head # => Some(1)
66
- M::List[1, 2, 3, 4].tail # => List[2, 3, 4]
67
- ```
68
-
69
- ### `traverse`
70
-
71
- Traverses the list with a block (or without it). This method "flips" List structure with the given monad (obtained from the type).
72
-
73
- **Note that traversing requires the list to be typed.**
74
-
75
- ```ruby
76
- require 'dry/monads/list'
77
-
78
- M = Dry::Monads
79
-
80
- M::List[M::Success(1), M::Success(2)].typed(M::Result).traverse # => Success([1, 2])
81
- M::List[M::Maybe(1), M::Maybe(nil), M::Maybe(3)].typed(M::Maybe).traverse # => None
82
-
83
- # also, you can use fmap with #traverse
84
-
85
- M::List[1, 2].fmap { |x| M::Success(x) }.typed(M::Result).traverse # => Success([1, 2])
86
- M::List[1, nil, 3].fmap { |x| M::Maybe(x) }.typed(M::Maybe).traverse # => None
87
- ```
@@ -1,146 +0,0 @@
1
- ---
2
- title: Maybe
3
- layout: gem-single
4
- name: dry-monads
5
- ---
6
-
7
- The `Maybe` monad is used when a series of computations could return `nil` at any point.
8
-
9
- ### `bind`
10
-
11
- Applies a block to a monadic value. If the value is `Some` then calls the block passing the unwrapped value as an argument. Returns itself if the value is `None`.
12
-
13
- ```ruby
14
- extend Dry::Monads[:maybe]
15
-
16
- maybe_user = Maybe(user).bind do |u|
17
- Maybe(u.address).bind do |a|
18
- Maybe(a.street)
19
- end
20
- end
21
-
22
- # If user with address exists
23
- # => Some("Street Address")
24
- # If user or address is nil
25
- # => None()
26
-
27
- # You also can pass a proc to #bind
28
-
29
- add_two = -> (x) { Maybe(x + 2) }
30
-
31
- Maybe(5).bind(add_two).bind(add_two) # => Some(9)
32
- Maybe(nil).bind(add_two).bind(add_two) # => None()
33
-
34
- ```
35
-
36
- ### `fmap`
37
-
38
- Similar to `bind` but works with blocks/methods that returns unwrapped values (i.e. not `Maybe` instances).
39
-
40
- ```ruby
41
- extend Dry::Monads[:maybe]
42
-
43
- Maybe(10).fmap { |x| x + 5 }.fmap { |y| y * 2 }
44
- # => Some(30)
45
- ```
46
-
47
- In 1.x `Maybe#fmap` coerces `nil` values returned from blocks to `None`. This behavior will be changed in 2.0. This will be done because implicit coercion violates the functor laws which in order can lead to a surpising (not in a good sense) behavior. If you expect a block to return `nil`, use `Maybe#maybe` added in 1.3.
48
-
49
- ### `maybe`
50
-
51
- Almost identical to `Maybe#fmap` but maps `nil` to `None`. This is similar to how the `&.` operator works in Ruby but does wrapping:
52
-
53
- ```ruby
54
- extend Dry::Monads[:maybe]
55
-
56
- Maybe(user).maybe(&:address).maybe(&:street)
57
-
58
- # If user with address exists
59
- # => Some("Street Address")
60
- # If user or address is nil
61
- # => None()
62
- ```
63
-
64
- ### `value!`
65
-
66
- You always can extract the result by calling `value!`. It will raise an error if you call it on `None`. You can use `value_or` for safe unwrapping.
67
-
68
- ```ruby
69
- extend Dry::Monads[:maybe]
70
-
71
- Some(5).fmap(&:succ).value! # => 6
72
-
73
- None().fmap(&:succ).value!
74
- # => Dry::Monads::UnwrapError: value! was called on None
75
-
76
- ```
77
-
78
- ### `value_or`
79
-
80
- Has one argument, unwraps the value in case of `Some` or returns the argument value back in case of `None`. It's a safe and recommended way of extracting values.
81
-
82
- ```ruby
83
- extend Dry::Monads[:maybe]
84
-
85
- add_two = -> (x) { Maybe(x + 2) }
86
-
87
- Maybe(5).bind(add_two).value_or(0) # => 7
88
- Maybe(nil).bind(add_two).value_or(0) # => 0
89
-
90
- Maybe(nil).bind(add_two).value_or { 0 } # => 0
91
- ```
92
-
93
- ### `or`
94
-
95
- The opposite of `bind`.
96
-
97
- ```ruby
98
- extend Dry::Monads[:maybe]
99
-
100
- add_two = -> (x) { Maybe(x + 2) }
101
-
102
- Maybe(5).bind(add_two).or(Some(0)) # => Some(7)
103
- Maybe(nil).bind(add_two).or(Some(0)) # => Some(0)
104
-
105
- Maybe(nil).bind(add_two).or { Some(0) } # => Some(0)
106
- ```
107
-
108
- ### `and`
109
-
110
- Two values can be chained using `.and`:
111
-
112
- ```ruby
113
- extend Dry::Monads[:maybe]
114
-
115
- Some(5).and(Some(10)) { |x, y| x + y } # => Some(15)
116
- Some(5).and(None) { |x, y| x + y } # => None()
117
- None().and(Some(10)) { |x, y| x + y } # => None()
118
-
119
- Some(5).and(Some(10)) # => Some([5, 10])
120
- Some(5).and(None()) # => None()
121
- ```
122
-
123
- ### `flatten`
124
-
125
- To remove one level of nesting:
126
-
127
- ```ruby
128
- extend Dry::Monads[:maybe]
129
-
130
- Some(Some(10)).flatten # => Some(10)
131
- Some(None()).flatten # => None()
132
- None().flatten # => None()
133
- ```
134
-
135
- ### `to_result`
136
-
137
- Maybe values can be converted to Result objects:
138
-
139
- ```ruby
140
- extend Dry::Monads[:maybe, :result]
141
-
142
- Some(10).to_result # => Success(10)
143
- None().to_result # => Failure()
144
- None().to_result(:error) # => Failure(:error)
145
- None().to_result { :block_value } # => Failure(:block_value)
146
- ```
@@ -1,68 +0,0 @@
1
- ---
2
- title: Pattern matching
3
- layout: gem-single
4
- name: dry-monads
5
- ---
6
-
7
- Ruby 2.7 introduces pattern matchings, it is nicely supported by dry-monads 1.3+.
8
-
9
- ### Matching Result values
10
-
11
- ```ruby
12
- # presumably you do it in a class with `include Dry::Monads[:result]`
13
-
14
- case value
15
- in Success(Integer => x)
16
- # x is bound to an integer
17
- in Success[:created, user] # alternatively: Success([:created, user])
18
- # user is bound to the second member
19
- in Success(Date | Time)
20
- # date or time object
21
- in Success([1, *])
22
- # any array starting with 1
23
- in Success(String => s) if s.size < 100
24
- # only if `s` is short enough
25
- in Success({ counter: Integer })
26
- # matches Success(counter: 50)
27
- # doesn't match Success(counter: 50, extra: 50)
28
- in Success({ user: User, account: Account => user_account, ** })
29
- # matches Success(user: User.new(...), account: Account.new(...), else: ...)
30
- # user_account is bound to the value of the `:account` key
31
- in Success()
32
- # corresponds to Success(Unit)
33
- in Success(_)
34
- # general success
35
- in Failure[:user_not_found]
36
- # Failure([:user_not_found])
37
- in Failure[error_code, *payload]
38
- # ...
39
- end
40
- ```
41
-
42
- In the sippet above, the patterns will be tried sequentially. If `value` doesn't match any pattern, an error will be thrown.
43
-
44
- ### Matching Maybe
45
-
46
- ```ruby
47
- case value
48
- in Some(Integer => x)
49
- # x is an integer
50
- in Some(Float | String)
51
- # ...
52
- in None
53
- # ...
54
- end
55
- ```
56
-
57
- ### Matching List
58
-
59
- ```ruby
60
- case value
61
- in List[Integer]
62
- # any list of size 1 with an integer
63
- in List[1, 2, 3, *]
64
- # list with size >= 3 starting with 1, 2, 3
65
- in List[]
66
- # empty list
67
- end
68
- ```
@@ -1,190 +0,0 @@
1
- ---
2
- title: Result
3
- layout: gem-single
4
- name: dry-monads
5
- ---
6
-
7
- The `Result` monad is useful to express a series of computations that might
8
- return an error object with additional information.
9
-
10
- The `Result` mixin has two type constructors: `Success` and `Failure`. The `Success`
11
- can be thought of as "everything went success" and the `Failure` is used when
12
- "something has gone wrong".
13
-
14
- ### `bind`
15
-
16
- Use `bind` for composing several possibly-failing operations:
17
-
18
- ```ruby
19
- require 'dry/monads'
20
-
21
- class AssociateUser
22
- include Dry::Monads[:result]
23
-
24
- def call(user_id:, address_id:)
25
- find_user(user_id).bind do |user|
26
- find_address(address_id).fmap do |address|
27
- user.update(address_id: address.id)
28
- end
29
- end
30
- end
31
-
32
- private
33
-
34
- def find_user(id)
35
- user = User.find_by(id: id)
36
-
37
- if user
38
- Success(user)
39
- else
40
- Failure(:user_not_found)
41
- end
42
- end
43
-
44
- def find_address(id)
45
- address = Address.find_by(id: id)
46
-
47
- if address
48
- Success(address)
49
- else
50
- Failure(:address_not_found)
51
- end
52
- end
53
- end
54
-
55
- AssociateUser.new.(user_id: 1, address_id: 2)
56
- ```
57
-
58
- ### `fmap`
59
-
60
- An example of using `fmap` with `Success` and `Failure`.
61
-
62
- ```ruby
63
- extend Dry::Monads[:result]
64
-
65
- result = if foo > bar
66
- Success(10)
67
- else
68
- Failure("wrong")
69
- end.fmap { |x| x * 2 }
70
-
71
- # If everything went success
72
- result # => Success(20)
73
- # If it did not
74
- result # => Failure("wrong")
75
-
76
- # #fmap accepts a proc, just like #bind
77
-
78
- upcase = :upcase.to_proc
79
-
80
- Success('hello').fmap(upcase) # => Success("HELLO")
81
- ```
82
-
83
- ### `value_or`
84
-
85
- `value_or` is a safe and recommended way of extracting values.
86
-
87
- ```ruby
88
- extend Dry::Monads[:result]
89
-
90
- Success(10).value_or(0) # => 10
91
- Failure('Error').value_or(0) # => 0
92
- ```
93
-
94
- ### `value!`
95
-
96
- If you're 100% sure you're dealing with a `Success` case you might use `value!` for extracting the value without providing a default. Beware, this will raise an exception if you call it on `Failure`.
97
-
98
- ```ruby
99
- extend Dry::Monads[:result]
100
-
101
- Success(10).value! # => 10
102
- Failure('Error').value!
103
- # => Dry::Monads::UnwrapError: value! was called on Failure
104
- ```
105
-
106
- ### `or`
107
-
108
- An example of using `or` with `Success` and `Failure`.
109
-
110
- ```ruby
111
- extend Dry::Monads[:result]
112
-
113
- Success(10).or(Success(99)) # => Success(10)
114
- Failure("error").or(Failure("new error")) # => Failure("new error")
115
- Failure("error").or { |err| Failure("new #{err}") } # => Failure("new error")
116
- ```
117
-
118
- ### `failure`
119
-
120
- Use `failure` for unwrapping the value from a `Failure` instance.
121
-
122
- ```ruby
123
- extend Dry::Monads[:result]
124
-
125
- Failure('Error').failure # => "Error"
126
- ```
127
-
128
- ### `to_maybe`
129
-
130
- Sometimes it's useful to turn a `Result` into a `Maybe`.
131
-
132
- ```ruby
133
- extend Dry::Monads[:result, :maybe]
134
-
135
- result = if foo > bar
136
- Success(10)
137
- else
138
- Failure("wrong")
139
- end.to_maybe
140
-
141
- # If everything went success
142
- result # => Some(10)
143
- # If it did not
144
- result # => None()
145
- ```
146
-
147
- ### `failure?` and `success?`
148
-
149
- You can explicitly check the type by calling `failure?` or `success?` on a monadic value.
150
-
151
- ### `either`
152
-
153
- `either` maps a `Result` to some type by taking two callables, for `Success` and `Failure` cases respectively:
154
-
155
- ```ruby
156
- Success(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 2
157
- Failure(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 3
158
- ```
159
-
160
-
161
- ### Adding constraints to `Failure` values.
162
- You can add type constraints to values passed to `Failure`. This will raise an exception if value doesn't meet the constraints:
163
-
164
- ```ruby
165
- require 'dry-types'
166
-
167
- module Types
168
- include Dry.Types()
169
- end
170
-
171
- class Operation
172
- Error = Types.Instance(RangeError)
173
- include Dry::Monads::Result(Error)
174
-
175
- def call(value)
176
- case value
177
- when 0..1
178
- Success(:success)
179
- when -Float::INFINITY..0, 1..Float::INFINITY
180
- Failure(RangeError.new('Error'))
181
- else
182
- Failure(TypeError.new('Type error'))
183
- end
184
- end
185
- end
186
-
187
- Operation.new.call(0.5) # => Success(:success)
188
- Operation.new.call(5) # => Failure(#<RangeError: Error>)
189
- Operation.new.call("5") # => Dry::Monads::InvalidFailureTypeError: Cannot create Failure from #<TypeError: Type error>, it doesn't meet the constraints
190
- ```