forked from dry-rb/dry-monads
-
Notifications
You must be signed in to change notification settings - Fork 0
/
changelog.yml
534 lines (474 loc) · 20.6 KB
/
changelog.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
---
- version: 1.6.0
date: 2022-11-04
changed:
- "This version uses dry-core 1.0 (@flash-gordon)"
- version: 1.5.0
date: 2022-10-16
changed:
- "Use zeitwerk for auto-loading dry-monads classes (@flash-gordon)"
- "`Task#then` is deprecated in favor of `Task#bind` (@flash-gordon)"
- "Minimal Ruby version is now 2.7 (@flash-gordon)"
- "Either (old name of Result) was removed (@flash-gordon)"
- version: 1.4.0
date: 2021-07-20
fixed:
- Do notation preserves method visibility (anicholson + flash-gordon)
changed:
- |-
Coercing `nil` values to `None` with `Some#fmap` is officially deprecated. (flash-gordon)
Switch to `Some#maybe` when you expect `nil`.
This behavior will be dropped in 2.0 but you can opt out of warnings for the time being
```ruby
Dry::Monads::Maybe.warn_on_implicit_nil_coercion false
```
- Minimal Ruby version is 2.6
added:
- "`Unit` destructures to an empty array (flash-gordon)"
- |-
When `.value!` called on a `Failure` value the error references to the value (rewritten + flash-gordon)
```ruby
begin
Failure("oops").value!
rescue => error
error.receiver # => Failure("oops")
end
```
- |-
`Result#alt_map` for mapping failure values (flash-gordon)
```ruby
Failure("oops").alt_map(&:upcase) # => Failure("OOPS")
```
- |-
`Try#recover` recovers from errors (flash-gordon)
```ruby
error = Try { Hash.new.fetch(:missing) }
error.recover(KeyError) { 'default' } # => Try::Value("default")
```
- |-
`Maybe#filter` runs a predicate against the wrapped value. Returns `None` if the result is false (flash-gordon)
```ruby
Some(3).filter(&:odd?) # => Some(3)
Some(3).filter(&:even?) # => None
# no block given
Some(3 == 5).filter # => None
```
- |-
`RightBiased#|` is an alias for `#or` (flash-gordon)
```ruby
None() | Some(6) | Some(7) # => Some(6)
Failure() | Success("one") | Success("two") # => Success("one")
```
- version: 1.3.5
date: "2020-01-06"
added:
- Smarter keys deconstruction in pattern matching (flash-gordon)
- version: 1.3.4
date: "2019-12-28"
fixed:
- One more delegation warning happenning in do notation (flash-gordon)
- version: 1.3.3
date: "2019-12-11"
fixed:
- Incompatibility with Rails. Internal (!) halt exceptions now use mutable backtraces
because spring [mutates](https://github.com/rails/spring/blob/ee687859008e947bc905b95121e306e2948d31c9/lib/spring/application.rb#L295-L311)
(!) them. For the record, this a bug in Rails (johnmaxwell)
- version: 1.3.2
date: "2019-11-30"
fixed:
- Warnings about keywords from Ruby 2.7 (flash-gordon)
added:
- |-
Pattern matching syntax was improved by implementing `#deconstruct_keys`. Now curly braces aren't necessary when the wrapped value is a Hash (flash-gordon)
```ruby
case result
in Success(code: 200...300) then :ok
end
```
- "## Internal"
- Performance of do notation was improved for failing cases (1.2x to 1.3x on synthetic
benchmarks) (flash-gordon)
- version: 1.3.1
date: "2019-09-07"
fixed:
- "Added missing `None#maybe` :sweat_smile: (flash-gordon)"
- version: 1.3.0
date: "2019-08-03"
added:
- |-
`Result#either` (waiting-for-dev)
```ruby
Success(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 2
Failure(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 3
```
- |-
`Maybe#to_result` (SpyMachine + flash-gordon)
```ruby
Some(3).to_result(:no_value) # => Success(3)
None().to_result { :no_value } # => Failure(:no_value)
None().to_result # => Failure()
```
- |-
Do notation can be used with `extend`. This simplifies usage in class methods and in other "complicated" cases (gogiel + flash-gordon)
```ruby
class CreateUser
extend Dry::Monads::Do::Mixin
extend Dry::Monads[:result]
def self.run(params)
self.call do
values = bind Validator.validate(params)
user = bind UserRepository.create(values)
Success(user)
end
end
end
```
Or you can bind values directly:
```ruby
ma = Dry::Monads.Success(1)
mb = Dry::Monads.Success(2)
Dry::Monads::Do.() do
a = Dry::Monads::Do.bind(ma)
b = Dry::Monads::Do.bind(mb)
Dry::Monads.Success(a + b)
end
```
- |-
`{Some,Success,Failure}#[]` shortcuts for building arrays wrapped within monadic value (flash-gordon)
```ruby
Success[1, 2] # => Success([1, 2])
```
- |-
`List.unfold` yields a block returning `Maybe<Any>`. If the block returns `Some(a)` `a` is appended to the output list. Returning `None` halts the unfloding (flash-gordon)
```ruby
List.unfold(0) do |x|
if x > 5
None()
else
Some[x + 1, 2**x]
end
end # => List[1, 2, 3, 4, 5]
```
- |-
Experimental support for pattern matching! :tada: (flash-gordon)
```ruby
case value
in Failure(_) then :failure
in Success(10) then :ten
in Success(100..500 => code) then code
in Success() then :empty
in Success(:code, x) then x
in Success[:status, x] then x
in Success({ status: x }) then x
in Success({ code: 200..300 => x }) then x
end
```
Read more about pattern matching in Ruby:
- https://medium.com/@baweaver/ruby-2-7-pattern-matching-destructuring-on-point-90f56aaf7b4e
- https://bugs.ruby-lang.org/issues/14912
Keep in mind this feature is experimental and can be changed by 2.7 release. But it rocks already!
- version: 1.2.0
date: "2019-01-12"
added:
- |-
Most of constructors now have `call` alias so you can compose them with Procs nicely if you've switched to Ruby 2.6 (flash-gordon)
```ruby
pipe = -> x { x.upcase } >> Success
pipe.('foo') # => Success('FOO')
```
- |-
`List#collect` gathers `Some` values from the list (flash-gordon)
```ruby
include Dry::Monads::List::Mixin
include Dry::Monads::Maybe::Mixin
# ...
List[10, 5, 0].collect do |divisor|
if divisor.zero?
None()
else
Some(n / divisor)
end
end
# => List[4, 2]
```
Without block:
```ruby
List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
# => [10, 6]
```
- |-
Right-biased monads got `#flatten` and `#and` (falsh-gordon)
`#flatten` removes one level of monadic structure, it's useful when you're dealing with things like `Maybe` of `Maybe` of something:
```ruby
include Dry::Monads::Maybe::Mixin
Some(Some(1)).flatten # => Some(1)
Some(None()).flatten # => None
None().flatten # => None
```
In contrast to `Array#flatten`, dry-monads' version removes only 1 level of nesting, that is always acts as `Array#flatten(1)`:
```ruby
Some(Some(Some(1))).flatten # => Some(Some(1))
```
`#and` is handy for combining two monadic values and working with them at once:
```ruby
include Dry::Monads::Maybe::Mixin
# using block
Some(5).and(Some(3)) { |x, y| x + y } # => Some(8)
# without block
Some(5).and(Some(3)) # => Some([5, 3])
# other cases
Some(5).and(None()) # => None()
None().and(Some(5)) # => None()
```
- |-
Concise imports with `Dry::Monads.[]`. You're no longer required to require all desired monads and include them one-by-one, the `[]` method handles it for you (flash-gordon)
```ruby
require 'dry/monads'
class CreateUser
include Dry::Monads[:result, :do]
def initialize(repo, send_email)
@repo = repo
@send_email = send_email
end
def call(name)
if @repo.user_exist?(name)
Failure(:user_exists)
else
user = yield @repo.add_user(name)
yield @send_email.(user)
Success(user)
end
end
end
```
- "`Task.failed` is a counterpart of `Task.pure`, accepts an exception and returns
a failed task immediately (flash-gordon)"
- version: 1.1.0
date: "2018-10-16"
fixed:
- Do notation was made to work nicely with inheritance. This shouldn't break any
existing code but if it does please report (flash-gordon)
added:
- |-
`Success()`, `Failure()`, and `Some()` now have `Unit` as a default argument:
```ruby
include Dry::Monads::Result::Mixin
include Dry::Monads::Do
def call
yield do_1
yield do_2
Success() # returns Success(Unit)
end
```
- version: 1.0.1
date: "2018-08-11"
fixed:
- Fixed behavior of `List<Validated>#traverse` in presence of `Valid` values (flash-gordon
+ SunnyMagadan)
added:
- |-
`to_proc` was added to value constructors (flash-gordon)
```ruby
[1, 2, 3].map(&Some) # => [Some(1), Some(2), Some(3)]
```
- version: 1.0.0
date: "2018-06-26"
added:
- |-
`do`-like notation (the idea comes from Haskell of course). This is the biggest and most important addition to the release which greatly increases the ergonomics of using monads in Ruby. Basically, almost everything it does is passing a block to a given method. You call `yield` on monads to extract the values. If any operation fails i.e. no value can be extracted, the whole computation is halted and the failing step becomes a result. With `Do` you don't need to chain monadic values with `fmap/bind` and block, everything can be done on a single level of indentation. Here is a more or less real-life example:
```ruby
class CreateUser
include Dry::Monads
include Dry::Monads::Do.for(:call)
attr_reader :user_repo
def initialize(:user_repo)
@user_repo = user_repo
end
def call(params)
json = yield parse_json(params)
hash = yield validate(json)
user_repo.transaction do
user = yield create_user(hash[:user])
yield create_profile(user, hash[:profile])
Success(user)
end
end
private
def parse_json(params)
Try[JSON::ParserError] {
JSON.parse(params)
}.to_result
end
def validate(json)
UserSchema.(json).to_monad
end
def create_user(user_data)
Try[Sequel::Error] { user_repo.create(user_data) }.to_result
end
def create_profile(user, profile_data)
Try[Sequel::Error] {
user_repo.create_profile(user, profile_data)
}.to_result
end
end
```
In the code above any `yield` can potentially fail and return the failure reason as a result. In other words, `yield None` acts as `return None`. Internally, `Do` uses exceptions, not `return`, this is somewhat slower but allows to detect failed operations in DB-transactions and roll back the changes which far more useful than an unjustifiable speed boost (flash-gordon)
- |-
The `Task` monad based on `Promise` from the [`concurrent-ruby` gem](https://github.com/ruby-concurrency/concurrent-ruby/). `Task` represents an asynchronous computation which _can be_ (doesn't have to!) run on a separated thread. `Promise` already offers a good API and implemented in a safe manner so `dry-monads` just adds a monad-compatible interface for it. Out of the box, `concurrent-ruby` has three types of executors for running blocks: `:io`, `:fast`, `:immediate`, check out [the docs](http://ruby-concurrency.github.io/concurrent-ruby/root/Concurrent.html#executor-class_method) for details. You can provide your own executor if needed (flash-gordon)
```ruby
include Dry::Monads::Task::Mixin
def call
name = Task { get_name_via_http } # runs a request in the background
email = Task { get_email_via_http } # runs another one request in the background
# to_result forces both computations/requests to complete by pausing current thread
# returns `Result::Success/Result::Failure`
name.bind { |n| email.fmap { |e| create(e, n) } }.to_result
end
```
`Task` works perfectly with `Do`
```ruby
include Dry::Monads::Do.for(:call)
def call
name, email = yield Task { get_name_via_http }, Task { get_email_via_http }
Success(create(e, n))
end
```
- "`Lazy` is a copy of `Task` that isn't run until you ask for the value _for the
first time_. It is guaranteed the evaluation is run at most once as opposed to
lazy assignment `||=` which isn't synchronized. `Lazy` is run on the same thread
asking for the value (flash-gordon)"
- |-
Automatic type inference with `.typed` for lists was deprecated. Instead, typed list builders were added
```ruby
list = List::Task[Task { get_name }, Task { get_email }]
list.traverse # => Task(List['John', 'john@doe.org'])
```
The code above runs two tasks in parallel and automatically combines their results with `traverse` (flash-gordon)
- |-
`Try` got a new call syntax supported in Ruby 2.5+
```ruby
Try[ArgumentError, TypeError] { unsafe_operation }
```
Prior to 2.5, it wasn't possible to pass a block to `[]`.
- |-
The `Validated` “monad” that represents a result of a validation. Suppose, you want to collect all the errors and return them at once. You can't have it with `Result` because when you `traverse` a `List` of `Result`s it returns the first value and this is the correct behavior from the theoretical point of view. `Validated`, in fact, doesn't have a monad instance but provides a useful variant of applicative which concatenates the errors.
```ruby
include Dry::Monads
include Dry::Monads::Do.for(:call)
def call(input)
name, email = yield [
validate_name(input[:name]),
validate_email(input[:email])
]
Success(create(name, email))
end
# can return
# * Success(User(...))
# * Invalid(List[:invalid_name])
# * Invalid(List[:invalid_email])
# * Invalid(List[:invalid_name, :invalid_email])
```
In the example above an array of `Validated` values is implicitly coerced to `List::Validated`. It's supported because it's useful but don't forget it's all about types so don't mix different types of monads in a single array, the consequences are unclear. You always can be explicit with `List::Validated[validate_name(...), ...]`, choose what you like (flash-gordon).
- |-
`Failure`, `None`, and `Invalid` values now store the line where they were created. One of the biggest downsides of dealing with monadic code is lack of backtraces. If you have a long list of computations and one of them fails how do you know where did it actually happen? Say, you've got `None` and this tells you nothing about _what variable_ was assigned to `None`. It makes sense to use `Result` instead of `Maybe` and use distinct errors everywhere but it doesn't always look good and forces you to think more. TLDR; call `.trace` to get the line where a fail-case was constructed
```ruby
Failure(:invalid_name).trace # => app/operations/create_user.rb:43
```
- |-
`Dry::Monads::Unit` which can be used as a replacement for `Success(nil)` and in similar situations when you have side effects yet doesn't return anything meaningful as a result. There's also the `.discard` method for mapping any successful result (i.e. `Success(?)`, `Some(?)`, `Value(?)`, etc) to `Unit`.
```ruby
# we're making an HTTP request but "forget" any successful result,
# we only care if the task was complete without an error
Task { do_http_request }.discard
# ... wait for the task to finish ...
# => Task(valut=Unit)
```
- "## Deprecations"
- "`Either`, the former name of `Result`, is now deprecated"
- "## BREAKING CHANGES"
- "`Either#value` and `Maybe#value` were both droped, use `value_or` or `value!`
when you :100: sure it's safe"
- "`require 'dry/monads'` doesn't load all monads anymore, use `require 'dry/monads/all'`
instead or cherry pick them with `require 'dry/monads/maybe'` etc (timriley)"
- version: 0.4.0
date: "2017-11-11"
changed:
- 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)
- Consequently, `Try::Success` and `Try::Failure` were renamed to `Try::Value` and
`Try::Error` (flash-gordon)
added:
- "`Try#or`, works as `Result#or` (flash-gordon)"
- "`Maybe#success?` and `Maybe#failure?` (aliases for `#some?` and `#none?`) (flash-gordon)"
- "`Either#flip` inverts a `Result` value (flash-gordon)"
- "`List#map` called without a block returns an `Enumerator` object (flash-gordon)"
- |-
Right-biased monads (`Maybe`, `Result`, and `Try`) now implement the `===` operator which is used for equality checks in the `case` statement (flash-gordon)
```ruby
case value
when Some(1..100) then :ok
when Some { |x| x < 0 } then :negative
when Some(Integer) then :invalid
else raise TypeError
end
```
- "## Deprecated"
- 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 Failure/None/Error
instance (flash-gordon)
- version: 0.3.1
date: "2017-03-18"
fixed:
- Fixed unexpected coercing to `Hash` on `.bind` call (flash-gordon)
- version: 0.3.0
date: "2017-03-16"
added:
- Added `Either#either` that accepts two callbacks, runs the first if it is `Right`
and the second otherwise (nkondratyev)
- Added `#fmap2` and `#fmap3` for mapping over nested structures like `List Either`
and `Either Some` (flash-gordon)
- Added `Try#value_or` (dsounded)
- 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)
- "`#bind` made to work with keyword arguments as extra parameters to the block
(flash-gordon)"
- Added `List#traverse` that "flips" the list with an embedded monad (flash-gordon
+ damncabbage)
- Added `#tee` for all right-biased monads (flash-gordon)
- version: 0.2.1
date: "2016-11-13"
added:
- Added `Either#tee` that is similar to `Object#tap` but executes the block only
for `Right` instances (saverio-kantox)
fixed:
- "`Right(nil).to_maybe` now returns `None` with a warning instead of failing (orisaka)"
- "`Some#value_or` doesn't require an argument because `None#value_or` doesn't require
it either if a block was passed (flash-gordon)"
- version: 0.2.0
date: "2016-09-18"
added:
- Added `Maybe#to_json` as an opt-in extension for serialization to JSON (rocknruby)
- Added `Maybe#value_or` which returns you the underlying value with a fallback
in a single method call (dsounded)
- version: 0.1.1
date: "2016-08-25"
fixed:
- Added explicit requires of `dry-equalizer`. This allows to safely load only specific
monads (artofhuman)
- version: 0.1.0
date: "2016-08-23"
added:
- Support for passing extra arguments to the block in `.bind` and `.fmap` (flash-gordon)
changed:
- Dropped MRI 2.0 support (flash-gordon)
- version: 0.0.2
date: "2016-06-29"
added:
- Added `Either#to_either` so that you can rely on duck-typing when you work with
different types of monads (timriley)
- Added `Maybe#to_maybe` for consistency with `#to_either` (flash-gordon)
- version: 0.0.1
date: "2016-05-02"
summary: Initial release containing `Either`, `Maybe`, and `Try` monads.