mayak 0.2.0 → 0.2.2

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.
@@ -1,1364 +0,0 @@
1
- # Monads
2
-
3
- ## Table of Contents
4
- * [Description](#description)
5
- * [Maybe](#maybe)
6
- * [Try](#try)
7
- * [Result](#result)
8
-
9
- ## Description
10
-
11
- This module is introduced to replace `dry-monads` with custom monads implementation which plays better with sorbet
12
- type-checking. If you haven't worked with monads and don't know what's that, check [dry-monads documentation](https://dry-rb.org/gems/dry-monads/1.3/) first.
13
- This should help you to get an idea of monads.
14
-
15
- Now let's dive deeper into different kinds of monads.
16
-
17
- ## Maybe
18
-
19
- This is probably the simplest monad that represents a series of computations could return `nil` at any point.
20
- This monad is [parameterized](https://sorbet.org/docs/generics) with a type of value. Basically, the `Maybe` is supertype
21
- of two subtypes: `Some` and `None` that shares the same interface. `Some` subtype contains a value, `None` doesn't contain
22
- anything and represents `nil`.
23
-
24
- ### Initialization
25
-
26
- The monad can be created with two primary constructors `Maybe` and `None` (note that these are methods).
27
- Method `#Maybe` wraps a nilable value with a `Maybe` class: if a value is nil, than it returns value of `None`,
28
- if the value is present, it returns instance of `Some`. In order to access these helpers`Mayak::Monads::Maybe::Mixin` must be included first:
29
-
30
- ```ruby
31
- include Mayak::Monads::Maybe::Mixin
32
-
33
- sig { params(numbers: T::Array[Integer]).returns(T.nilable(Integer)) }
34
- def first(numbers)
35
- numbers.first
36
- end
37
-
38
- Maybe(first[]) # None
39
- Maybe(first[1]) # Some[Integer](value = 1)
40
- ```
41
-
42
- Also, the monad can be instantiated directly with `Mayak::Monads::Maybe::Some` and `Mayak::Monads::Maybe::None`:
43
-
44
- ```ruby
45
- sig { returns(Mayak::Monads::Maybe[Integer]) }
46
- def some
47
- Mayak::Monads::Maybe::Some[Integer].new(10)
48
- end
49
-
50
- sig { returns(Mayak::Monads::Maybe[Integer]) }
51
- def none
52
- Mayak::Monads::Maybe::None[Integer].new
53
- end
54
- ```
55
-
56
- ### Unpacking
57
-
58
- In order to retrieve a value from a `Maybe` a method `#value` which is defined on `Mayak::Monads::Maybe::Some` can be used.
59
- `Mayak::Monads::Maybe::Some` is a subtask of `Maybe`. Note that the accessor is only defined on `Some` subtype, so if the `#value`
60
- method will be called on an instance of `Maybe`, Sorbet will not type-check, since `Maybe` doesn't have this method.
61
- In order to access value of `Maybe`, it need to be verified first that the monad is instance of `Some`, and only after that
62
- this method can invoked. The most convenient way to do this is to use case-when statement.
63
-
64
- ```ruby
65
- sig { params(a: Integer, b: Integer).returns(Mayak::Monads::Maybe[Integer]) }
66
- def divide(a, b)
67
- if b == 0
68
- None()
69
- else
70
- Maybe(a / b)
71
- end
72
- end
73
-
74
- sig { params(a: Integer, b: Integer).void }
75
- def print_result_divide(a, b)
76
- result = divide(a, b)
77
- case result
78
- when Mayak::Monads::Maybe::Some
79
- # sorbet's flow-typing down-casts result to Mayak::Monads::Maybe::Some
80
- # so type of this variable is Mayak::Monads::Maybe::Some in this branch
81
- puts "#{a} / #{b} = #{result.value}"
82
- when Mayak::Monads::Maybe::None
83
- puts "Division by zero"
84
- else
85
- T.absurd(result)
86
- end
87
- end
88
-
89
- print_result_divide(10, 2) # 10 / 2 = 5
90
- print_result_divide(10, 0) # Division by zero
91
- ```
92
-
93
- The other way is to use method `#value_or` which returns either a value if the monad is `Some`, or
94
- fallback value if the monad is `None`:
95
-
96
- ```ruby
97
- divide(10, 2).value_or(0) # 5
98
- divide(10, 0).value_or(0) # 0
99
- ```
100
-
101
- Note that the type of value passed into `value_or` shouldn't necessarily be of the same type as `Maybe`. If it doesn't match
102
- the result type of method invokation will be union of `Maybe` type and type of value passed:
103
- ```ruby
104
- result = divide(10, 2).value_or("None")
105
- T.reveal_type(result) # T.any(String, Integer)
106
- ```
107
-
108
- You can also pass `nil` into `value_or`, and the result type of the expression will `T.nilable` type:
109
- ```ruby
110
- result = divide(10, 2).value_or(nil)
111
- T.reveal_type(result) # T.nilable(Integer)
112
- ```
113
-
114
- ### Methods
115
-
116
- #### `#either`
117
-
118
- Receives two lambda functions representing branches: none-branch, and some-branch. If a `Maybe` is `None` than none-branch will be executedand result of method invokation will be a result of none-branch execution. If a `Maybe` is `Some` than some-branch will be executed with a value of monad and result of method invokation will be a result of some-branch execution.
119
-
120
- ```ruby
121
- divide(10, 2).either(
122
- -> { 0 },
123
- -> (value) { value * 100 }
124
- ) # 50
125
-
126
-
127
- divide(10, 0).either(
128
- -> { 0 },
129
- -> (value) { value * 100 }
130
- ) # 0
131
-
132
- result = divide(10, 0).either(
133
- -> { "Error" },
134
- -> (value) { value * 100 }
135
- )
136
- T.reveal_type(result) # T.any(String, Integer)
137
- ```
138
-
139
- #### `#map`
140
-
141
- The same as `fmap` in a dry-monads `Maybe`. Allows to modify the value inside the `Maybe` with a block if it's present. Returns `Maybe` itself.
142
-
143
- ```ruby
144
- sig { returns(Mayak::Monads::Maybe[Integer]) }
145
- def some
146
- Mayak::Monads::Maybe::Some[Integer].new(10)
147
- end
148
-
149
- sig { returns(Mayak::Monads::Maybe[Integer]) }
150
- def none
151
- Mayak::Monads::Maybe::None[Integer].new
152
- end
153
-
154
- some.map { |a| a + 20 } # Some[Integer](value = 20)
155
- none.map { |a| a + 20 } # None[Integer]
156
- ```
157
-
158
- #### `#flat_map`
159
- The same as `bind` in a dry-monads `Maybe`. Allows to modify the value with a block that returns another `Maybe`
160
- if it's present, otherwise returns `None`. If the block returns `None`, the whole computation returns `None`.
161
-
162
- ```ruby
163
- sig { params(a: Integer, b: Integer).returns(Mayak::Monads::Maybe[Integer]) }
164
- def divide(a, b)
165
- if b.zero?
166
- Mayak::Monads::Maybe::None[Integer].new
167
- else
168
- Mayak::Monads::Maybe::Some[Integer].new(a / b)
169
- end
170
- end
171
-
172
- divide(20, 2).flat_map { |a| divide(a, 2) } # Some[Integer](value = 5)
173
- divide(20, 2).flat_map { |a| divide(a, 0) } # None
174
- divide(20, 0).flat_map { |a| divide(a, 2) } # None
175
- ```
176
-
177
- #### `#filter`
178
-
179
- Receives a block the returns a boolean value and checks the underlying value with the block when a monad is `Some`.
180
- Returns `None` if the block called on the value returns `false`, and returns `self` if the block returns `true`.
181
- Returns `None` if the monad is `None`.
182
-
183
- ```ruby
184
- divide(20, 2).filter { |value| value > 5 } # Some[Integer](value = 10)
185
- divide(20, 2).filter { |value| value < 5 } # None
186
- divide(20, 0).filter { |value| value < 5 } # None
187
- ```
188
-
189
- #### `#some?`
190
-
191
- Returns true if a `Maybe` is a `Some` and false if it's `None`.
192
-
193
- ```ruby
194
- divide(20, 2).some? # true
195
- divide(20, 0).some? # false
196
- ```
197
-
198
- #### `#none?`
199
-
200
- Returns true if a `Maybe` is a `None` and false if it's `Some`.
201
-
202
- ```ruby
203
- divide(20, 2).none? # false
204
- divide(20, 0).none? # true
205
- ```
206
-
207
- #### `#value_or`
208
-
209
- Unpack a `Maybe` and returns its value if it's a `Some`, or returns provided fallback value if it's a `None`.
210
-
211
- ```ruby
212
- divide(20, 2).value_or(0) # 10
213
- divide(20, 0).value_or(0) # 0
214
- ```
215
-
216
- #### `#to_task`
217
-
218
- Converts a value to task. If a `Maybe` is an instance of `Some`, it returns succeeded task, if it's a `None`,
219
- it returns failed task with a provided error.
220
-
221
- ```ruby
222
- task = Mayak::Concurrent::Task.execute { 100 }
223
- error = StandardError.new("Division by zero")
224
- task.flat_map { |value| divide(value, 10).to_task(error) }.await! # 10
225
- task.flat_map { |value| divide(value, 0).to_task(error) }.await! # StandardError: Divison by zero
226
- ```
227
-
228
- #### `#to_result`
229
-
230
- Converts a `Maybe` into a `Result`. If the `Maybe` is a `Some`, returns `Result::Success` with a value of the `Maybe`.
231
- If it's a `None`, returns `Result::Failre` with a value of an error provided as an argument.
232
-
233
- ```ruby
234
- divide(10, 2).to_result("Division by zero") # Success: Result[String, Integer](value = 5)
235
- divide(10, 0).to_result("Division by zero") # Failure: Result[String, Integer](error = "Division by zero")
236
- ```
237
-
238
- #### `#to_try`
239
-
240
- Converts a `Maybe` into a `Try`. If the `Maybe` is a `Some`, returns `Try::Success` with a value of the `Maybe`.
241
- If it's a `None`, returns `Try::Failre` with a value of an error provided as an argument.
242
-
243
- ```ruby
244
- divide(10, 2).to_try(StandardError.new("Division by zero")) # Success: Try[Integer](value = 5)
245
- divide(10, 0).to_try(StandardError.new("Division by zero")) # Failure: Try[Integer](error = StandardError(message = "Division by zero"))
246
- ```
247
-
248
- Combination of `Maybe` constructor with `#to_try` method allows to significantly simplify common
249
- pattern: checking nullability of a value, and returning error if the value is missing:
250
- ```ruby
251
- sig { params(user: User).returns(Try[Address]) }
252
- def get_address(user)
253
- address = user.contact.address
254
-
255
- if address.present?
256
- Success(address)
257
- else
258
- Failure(AddressMissingError.new("Missing address for User(id=#{user.id})"))
259
- end
260
- end
261
- ```
262
-
263
- With `Maybe` and `#to_try`:
264
-
265
- ```ruby
266
- sig { params(user: User).returns(Try[Address]) }
267
- def get_address(user)
268
- Maybe(user.contact.address).to_try(
269
- AddressMissingError.new("Missing address for User(id=#{user.id})")
270
- )
271
- end
272
- ```
273
-
274
- #### `#tee`
275
-
276
- If a `Maybe` is an instance of `Some`, runs a block with a value of `Some` passed, and returns the monad itself
277
- unchanged. Doesn't do anything if it's a `None`.
278
-
279
- ```ruby
280
- divide(10, 2).tee { |a| puts a }
281
- # returns: Some[Integer](value = 5)
282
- # console: 5
283
-
284
- divide(10, 0).tee { |a| puts a }
285
- # returns: None
286
- ```
287
-
288
- Can be useful to embed side-effects into chain of monad transformation:
289
- ```ruby
290
- sig { params(a: Integer, b: Integer).returns(Maybe[String]) }
291
- def run(a, b)
292
- divide(a, b)
293
- .tee { |value| logger.info("#{a} / #{b} = #{value}") }
294
- .map { |value| value * 100 }
295
- .tee { |value| logger.info("Intermediate result = #{value}") }
296
- .map(&:to_s)
297
- end
298
- ```
299
-
300
- #### `#recover`
301
-
302
- Converts `None` into a `Some` with a provided value. If the monad is an instance of `Some`, returns itself.
303
-
304
- ```ruby
305
- divide(10, 2).recover(0) # Some[Integer](value = 5)
306
- divide(10, 0).recover(0) # Some[Integer](value = 0)
307
- ```
308
-
309
- #### `.sequence`
310
- `Maybe.sequence` takes an array of `Maybe`s and transform it into a `Maybe` of an array.
311
- If all elements of an argument array is `Some`, then result will be `Some` of the array, otherwise
312
- it will `None`.
313
-
314
- ```ruby
315
- values = [Maybe(1), Maybe(2), Maybe(3)]
316
- Maybe.sequence(values) # Some([10, 5, 3])
317
-
318
- values = values = [Maybe(nil), Maybe(2), Maybe(3)]
319
- Maybe.sequence(values) # None
320
- ```
321
-
322
- #### `.check`
323
-
324
- Receives a value and block returning a boolean value. If the block returns true,
325
- the method returns the value wrapped in `Some`, otherwise it returns `None`:
326
-
327
- ```ruby
328
- Maybe.check(10) { 20 > 10 } # Some[Integer](10)
329
- Maybe.check(20) { 10 > 20 } # None
330
- ```
331
-
332
- #### `.guard`
333
-
334
- Receives a block returning a boolean value. If the block returns true,
335
- the method returns a `Some` containing `nil`, otherwise `None` is returned:
336
-
337
- ```ruby
338
- Maybe.guard { 20 > 10 } # Some[NilClass](nil)
339
- Maybe.guard { 10 > 20 } # None
340
- ```
341
-
342
- ### Do-notation
343
-
344
- Using `map` and `flat_map` for monads chaining can be really tedious, especially when computation
345
- requires combining different values from branches.
346
-
347
- Let's take a look at the following code snippet.
348
-
349
- ```ruby
350
- sig { abstract.params(id: Integer).returns(Maybe[User]) }
351
- def fetch_user(id)
352
- end
353
-
354
- sig { abstract.params(city: String, address: Address).returns(Maybe[Coordinates]) }
355
- def fetch_city_coordinates(city, address)
356
- end
357
-
358
- sig { abstract.params(user: User, coordinates: Coordinates).returns(Maybe[UserAddressCache]) }
359
- def fetch_user_address_cache(user, coordinates)
360
- end
361
-
362
- sig {
363
- abstract
364
- .params(user: User, address: Address, coordinates: Coordinates, cache: UserAddressCache)
365
- .returns(Maybe[UserAddressData])
366
- }
367
- def build_user_address_data(user, address, coordinates, cache)
368
- end
369
-
370
- sig { params(user_id: Integer).returns(Maybe[UserAddressData]) }
371
- def run(user_id)
372
- fetch_user(user_id).flat_map { |user|
373
- user
374
- .address
375
- .flat_map { |address|
376
- address
377
- .city
378
- .flat_map { |city| fetch_city_coordinates(city) }
379
- .flat_map { |coordinates|
380
- fetch_user_address_cache(user, coordinates).flat_map { |cache|
381
- build_user_address_data(user, address, coordinates, cache)
382
- }
383
- }
384
- }
385
- }
386
- end
387
- ```
388
-
389
- Don't worry if you can't really understand what's going on here, the code is intentionally overcomplicated to show you
390
- to which extremes `#map` and `#flat_map` can lead.
391
-
392
- In order to simplify computation you can use Do-notation. Let's see the same `run` method with using
393
- Do-notation instead of `#map` and `#flat_map` chaining, and let's break down how it's work:
394
-
395
- ```ruby
396
- # Make sure you have the Mixin included
397
- include Mayak::Monads::Maybe::Mixin
398
-
399
- sig { params(user_id: Integer).returns(Maybe[UserAddressCache]) }
400
- def run(user_id)
401
- for_maybe {
402
- user = do_maybe! fetch_user(user_id)
403
- address = do_maybe! user.address
404
- city = do_maybe! address.city
405
- coordinates = do_maybe! fetch_city_coordinates(city)
406
- cache = do_maybe! fetch_user_address_cache(user, coordinates)
407
- do_maybe! build_user_address_data(user, address, coordinates, cache)
408
- }
409
- end
410
- ```
411
-
412
- Do-notation basically consists from two methods `for_maybe` and `do_maybe!`. `for_maybe` creates
413
- a Do-notation scope within which you can use `do_maybe!` to unpack Monad values. If `do_maybe!` receives
414
- a `Some` value, it unpacks it and returns underlying values, if it receives a `None`, it short circuits execution,
415
- and the whole `for_maybe` block returns `None`. Otherwise `for_maybe` returns result of the last
416
- expression wrapped into `Maybe`.
417
-
418
- Let's check a few examples:
419
-
420
- ```ruby
421
- result = for_maybe {
422
- a = do_maybe! Maybe(10)
423
- puts a # Prints: 10
424
- b = do_maybe! Maybe(20)
425
- puts b # Prints: 20
426
- a + b
427
- }
428
- result # Some[Integer](value = 30)
429
-
430
- failure = for_maybe {
431
- a = do_maybe! Maybe(10)
432
- b = do_maybe! None # stops execution here
433
- puts "Not getting here" # Doesn't print anything
434
- a + b
435
- }
436
- failure # None
437
- ```
438
- You can also you methods `check_maybe!` and `guard_maybe!` to assert some invariants
439
- in do-notation blocks. These methods are specialized helpers of `Maybe.check` and `Maybe.guard` for
440
- more convenient usage in do-notation blocks:
441
-
442
- ```ruby
443
- user = for_maybe {
444
- user = do_maybe! fetch_user(user_id)
445
- company = do_maybe! fetch_company(company_id)
446
- # abrupt execution if block returns false
447
- # or return user if block returns false
448
- #
449
- # semantically equivalent to:
450
- # if user.works_in?(company)
451
- # do_maybe! Some(user)
452
- # else
453
- # do_maybe! None.new
454
- # end
455
- check_maybe!(user) { user.works_in?(company) }
456
- }
457
-
458
- for_maybe {
459
- api_key = do_maybe! Maybe(params[:api_key])
460
- # abrupt execution if block returns None
461
- #
462
- # semantically equivalent to:
463
- # if validate_api_key(api_key)
464
- # do_maybe! Some(nil)
465
- # else
466
- # do_maybe! None.new
467
- # end
468
- guard_maybe! { validate_api_key(api_key) }
469
-
470
- perform_query
471
- }
472
- ```
473
-
474
- A major improvement upon `dry-monads` do-notation, is that `Mayak::Monad`s do-notation if fully typed.
475
- Do-notations infers both result type of the whole computation, and a type of a value unwrapped by `do_maybe!`
476
-
477
- ```ruby
478
- for_maybe {
479
- value = do_maybe! Maybe(10)
480
- T.reveal_type(value)
481
- value
482
- }
483
- # > Revealed type: Integer
484
-
485
- result = for_maybe {
486
- value = do_maybe! Maybe(10)
487
- value
488
- }
489
- T.reveal_type(result)
490
- # > Revealed type: Integer
491
- ```
492
-
493
- ## Try
494
-
495
- `Try` monad represents result of a computation that can succeed with an arbitrary value, or fail with an error of a subtype of `StandardError`.
496
- `Try` has two subtypes: `Failure` which contains an instance of subtype of `StandardError` and represents failure case,
497
- and `Success`, which contains a success value of given type.
498
-
499
- ### Initialization
500
-
501
- The primary way to create an instance of `Try` is to use constructor method `#Try` from `Mayak::Monads::Try::Mixin`.
502
- This method receives a block that may raise exceptions. If an exception has been raised inside the block,
503
- the method will return instance of `Try::Failure` containing an error. Otherwise the method will return result of the block
504
- wrapped into `Try::Success`
505
-
506
- ```ruby
507
- include Mayak::Monads::Try::Mixin
508
-
509
- Try { 10 } # Try::Success[Integer](@value=10)
510
- Try {
511
- a = "Hello "
512
- b = "World!"
513
- a + b
514
- } # Try::Success[String](@value="Hello World!")
515
-
516
- Try {
517
- a = 10
518
- b = 0
519
- a / b
520
- } # Try::Failure[Integer](@failure=#<ZeroDivisionError: divided by 0>)
521
- ```
522
-
523
- Note that constructor is able to infer a type of value from type returned from the block:
524
-
525
- ```ruby
526
- sig { params(a: Integer, b: Integer).returns(Try[Integer]) }
527
- def divide(a, b)
528
- Try { a / b }
529
- end
530
- ```
531
-
532
- Exception should be an instance of `StandardError`s subtype to be captured:
533
- ```ruby
534
- Try { raise Exception.new("Not going to be captured") }
535
- # > Exception: Not going to be captured
536
- ```
537
-
538
- `#Try` can receive types of arguments to be captured. If exceptions of other types were raised, they won't
539
- captured. Types of exceptions should be subtypes of `StandardError`:
540
-
541
- ```ruby
542
- class CustomError < StandardError
543
- end
544
-
545
- Try(CustomError) {
546
- raise CustomError.new
547
- } # Try::Failure[T.noreturn](@failure=#<CustomError: CustomError>)
548
-
549
- Try(CustomError) {
550
- raise StandardError.new("Not going to be captured")
551
- }
552
- # > StandardError: Not going to be captured
553
- ```
554
-
555
- `Try` allows to specify multiple error types:
556
- ```ruby
557
- class CustomError1 < StandardError
558
- end
559
-
560
- class CustomError2 < StandardError
561
- end
562
-
563
- Try(CustomError1, CustomError2) {
564
- raise CustomError1.new
565
- } # Try::Failure[T.noreturn](@failure=#<CustomError: CustomError>)
566
-
567
- Try(CustomError1, CustomError2) {
568
- raise CustomError2.new
569
- } # Try::Failure[T.noreturn](@failure=#<CustomError: CustomError>)
570
-
571
- Try(CustomError1, CustomError2) {
572
- raise StandardError.new("Not going to be captured")
573
- }
574
- # > StandardError: Not going to be captured
575
- ```
576
-
577
- `Try` can also be initialized directly be invoking constructors of its subtypes:
578
- ```ruby
579
- Mayak::Monads::Try::Success[Integer].new(10) # Try::Success[Integer](@value=10)
580
- Mayak::Monads::Try::Failure[Integer].new(StandardError.new) # Try::Failure[Integer](@failure=#<StandardError: StandardError>)
581
- ```
582
-
583
- ### Unpacking
584
-
585
- Since `Try` can either contain success value in `Success` branch or error in `Failure` branch, there are specific accessors
586
- for success and failure values. In order to check whether a `Try` contains a successful value or value,
587
- predicates `#failure?` and `#success?` can be used:
588
-
589
- ```ruby
590
- Try { 10 }.success? # true
591
- Try { 10 }.failure? # false
592
-
593
- Try { raise "Error" }.success? # false
594
- Try { raise "Error" }.failure? # true
595
- ```
596
-
597
- In order to retrieve a successful value from `Try`, a method `#success` can be used. This method is only defined
598
- on `Mayak::Monads::Try::Success`, which is subtype of `Mayak::Monads::Try`. In order to invoke this method without getting an error from sorbet,
599
- instance of `Try` should be first coerced to checked and coerced to `Try::Success`. This can be done safely with pattern matching:
600
-
601
- ```ruby
602
- try = Try { 10 }
603
- value = case try
604
- when Mayak::Monads::Try::Success
605
- try.success
606
- else
607
- nil
608
- end
609
- value # 10
610
- ```
611
-
612
- If an instance of `Try` is a `Failure`, error can be retrieved via `#failure`:
613
-
614
- ```ruby
615
- try = Try { raise "Error" }
616
- value = case try
617
- when Mayak::Monads::Try::Failure
618
- try.failure
619
- when
620
- nil
621
- end
622
- value # #<StandardError: Error>
623
- ```
624
-
625
- Success and failure values can be retrieved via methods `#success_or` and `#failure_or` as well. These methods
626
- receives fallback values:
627
-
628
- ```ruby
629
- Try { 10 }.success_or(0) # 10
630
- Try { 10 }.failure_or(StandardError.new("Error")) # #<StandardError: Error>
631
-
632
- Try { raise "Boom" }.success_or(0) # 0
633
- Try { raise "Boom" }.failure_or(StandardError.new("Error")) # #<StandardError: Boom>
634
- ```
635
-
636
- ### Methods
637
-
638
- #### `#map`
639
-
640
- The same as `fmap` in a dry-monads `Try`. Allows to modify the value with a block if it's present.
641
-
642
- ```ruby
643
- sig { returns(Mayak::Monads::Try[Integer]) }
644
- def success
645
- Mayak::Monads::Try::Success[Integer].new(10)
646
- end
647
-
648
- sig { returns(Mayak::Monads::Try[Integer]) }
649
- def failure
650
- Mayak::Monads::Try::Failure[Integer].new(StandardError.new("Error"))
651
- end
652
-
653
- success.map { |a| a + 20 } # Success[Integer](value = 20)
654
- failure.map { |a| a + 20 } # Failure[Integer](error=#<StandardError: Error>)
655
- ```
656
-
657
- #### `#flat_map`
658
- The same as `bind` in a dry-monads `Try`. Allows to modify the value with a block that returns another `Try`
659
- if it's a`Success`, otherwise returns `Failure`. If the block returns `Failure`, the whole computation returns `Failure`.
660
-
661
- ```ruby
662
- sig { params(a: Integer, b: Integer).returns(Mayak::Monads::Try[Integer]) }
663
- def divide(a, b)
664
- Try { a / b }
665
- end
666
-
667
- divide(20, 2).flat_map { |a| divide(a, 2) } # Try::Success[Integer](value = 5)
668
- divide(20, 2).flat_map { |a| divide(a, 0) } # Try::Failure[Integer](@failure=#<ZeroDivisionError: divided by 0>)
669
- divide(20, 0).flat_map { |a| divide(a, 2) } # Try::Failure[Integer](@failure=#<ZeroDivisionError: divided by 0>)
670
- ```
671
-
672
- #### `#filter_or`
673
-
674
- Receives a block the returns a boolean value and checks the underlying value with the block when a monad is `Success`.
675
- Returns `Failure` with provided error if the block called on the value returns `false`, and returns `self` if the block returns `true`.
676
- Returns self if the original monad is `Failure`.
677
-
678
- ```ruby
679
- error = StandardError.new("Provided error")
680
- divide(20, 2).filter_or(error) { |value| value > 5 } # Try::Success[Integer](value = 5)
681
- divide(20, 2).filter_or(error) { |value| value < 5 } # Try::Failure[Integer](@failure=#<StandardError: Provided error>)
682
- divide(20, 0).filter_or(error) { |value| value < 5 } # Try::Failure[Integer](@failure=#<ZeroDivisionError: divided by 0>)
683
- ```
684
-
685
- #### `#success?`
686
-
687
- Returns true if a `Try` is a `Success` and false if it's a `Failure`.
688
-
689
- ```ruby
690
- divide(20, 2).success? # true
691
- divide(20, 0).success? # false
692
- ```
693
-
694
- #### `#failure?`
695
-
696
- Returns true if a `Try` is a `Failure` and false if it's a `Success`.
697
-
698
- ```ruby
699
- divide(20, 2).failure? # false
700
- divide(20, 0).failure? # true
701
- ```
702
-
703
- #### `#success_or`
704
-
705
- Unpack a `Try` and returns its success value if it's a `Success`, or returns provided fallback value if it's a `Failure`.
706
-
707
- ```ruby
708
- divide(20, 2).value_or(0) # 10
709
- divide(20, 0).value_or(0) # 0
710
- ```
711
-
712
- #### `#failure_or`
713
-
714
- Unpack a `Try` and returns its error if it's a `Failure`,
715
- or returns provided fallback error of `StandardError` subtype if it's a `Success`.
716
-
717
- ```ruby
718
- divide(20, 2).failure_or(StandardError.new("Error")) # #<StandardError: Error>
719
- divide(20, 0).failure_or(StandardError.new("Error")) # #<ZeroDivisionError: divided by 0>
720
- ```
721
-
722
- #### `#either`
723
-
724
- Receives two functions: one from an error to a result
725
- value (failure function), and another one from successful value to a result value (success function).
726
- If a `Try` is an instance of `Success`, applies success function to a success value,
727
- otherwise applies error function to an error. Note that both functions must have the same return type.
728
-
729
- ```ruby
730
- divide(10, 2).either(
731
- -> (error) { error.message },
732
- -> (value) { value.to_s }
733
- ) # 5
734
-
735
- divide(10, 0).either(
736
- -> (error) { error.message },
737
- -> (value) { value.to_s }
738
- ) # Division by zero
739
- ```
740
-
741
- #### `#tee`
742
-
743
- If a `Try` is an instance of `Success`, runs a block with a value of `Success` passed, and returns the monad itself
744
- unchanged. Doesn't do anything if the monad is an instance of `Failure`.
745
-
746
- ```ruby
747
- divide(10, 2).tee { |a| puts a }
748
- # returns: Try::Success[Integer](value = 5)
749
- # console: 5
750
-
751
- divide(10, 0).tee { |a| puts a }
752
- # returns: Try::Failure[Integer](@failure=#<ZeroDivisionError: divided by 0>)
753
- ```
754
-
755
- Can be useful to embed side-effects into chain of monad transformation:
756
- ```ruby
757
- sig { params(a: Integer, b: Integer).returns(Try[String]) }
758
- def run(a, b)
759
- divide(a, b)
760
- .tee { |value| logger.info("#{a} / #{b} = #{value}") }
761
- .map { |value| value * 100 }
762
- .tee { |value| logger.info("Intermediate result = #{value}") }
763
- .map(&:to_s)
764
- end
765
- ```
766
-
767
- #### `#map_failure`
768
-
769
- Transforms an error with a block if a monad is `Failure`. Returns itself otherwise. Note that the passed block
770
- should return an instance of `StandardError`, or an instance of a subtype of the `StandardError`.
771
-
772
- ```ruby
773
- divide(10, 0).map_failure { |error| CustomError.new(message) } # Try::Failure[Integer](@failure=#<CustomError: Division by zero>)
774
- divide(10, 2).map_failure { |error| CustomError.new(message) } # Try::Success[Integer](@value=5)
775
- ```
776
-
777
- #### `#flat_map_failure`
778
-
779
- Transforms an error with a block that returns an instance of `Try` if a monad is `Failure`. Returns itself otherwise.
780
- Allows to recover from error with a computation, that can fail too.
781
-
782
- ```ruby
783
- divide(10, 2).flat_map_failure { |_error|
784
- Mayak::Monads::Try::Success[Integer].new(10)
785
- } # Try::Success[Integer](@value=5)
786
-
787
- divide(10, 2).flat_map_failure { |error|
788
- Mayak::Monads::Try::Failure[Integer].new(StandardError.new(error.message))
789
- } # Try::Success[Integer](@value=5)
790
-
791
- divide(10, 0).flat_map_failure { |error|
792
- Mayak::Monads::Try::Success[Integer].new(10)
793
- } # Try::Success[Integer](@value=2)
794
-
795
- divide(10, 0).flat_map_failure { |error|
796
- Mayak::Monads::Try::Failure[Integer].new(CustomError.new(error.message))
797
- } # Try::Failure[Integer](@failure=#<CustomError: Division by zero>)
798
- ```
799
-
800
- #### `#to_result`
801
-
802
- Converts a `Try` into a `Result`. If the `Try` is a `Try::Success`, returns `Result::Success` with a success value.
803
- If it's a `Failure`, returns `Result::Failre` with a an error value.
804
-
805
- ```ruby
806
- divide(10, 2).to_result # Result::Success[StandardError, Integer](value = 5)
807
- divide(10, 0).to_result # Result::Failure[StandardError, Integer](error = #<ZeroDivisionError: divided by 0>)
808
- ```
809
-
810
- #### `#to_task`
811
-
812
- Converts a value to task. If a `Try` is an instance of `Success`, it returns succeeded task, if it's a `Failure`,
813
- it returns failed task with an `Failure`'s error.
814
-
815
- ```ruby
816
- task = Mayak::Concurrent::Task.execute { 100 }
817
- task.flat_map { |value| divide(value, 10).to_task }.await! # 10
818
- task.flat_map { |value| divide(value, 0).to_task }.await! # StandardError: Divison by zero
819
- ```
820
-
821
- #### `#to_maybe`
822
-
823
- Converts a `Try` value to `Maybe`. When the `Try` instance is a `Success`, returns `Maybe` containing successful value.
824
- When the `Try` is an instance of `Failure`, returns `None`. Note that during the transformation error is lost.
825
-
826
- ```ruby
827
- divide(10, 2).to_maybe # Some[Integer](value = 5)
828
- divide(10, 0).to_maybe # None[Integer]
829
- ```
830
-
831
- #### `#as`
832
-
833
- Substitutes successful value in `Try::Success` with a new value, if the monad is instance of `Try::Failure`, returns
834
- the monad unchanged.
835
-
836
- ```ruby
837
- divide(10, 2).as("Success")# Try::Success[String](@value="Success")
838
- divide(10, 0).as("Success") # Try::Failure[String](@failure=#<ZeroDivisionError: Division by zero>)
839
- ```
840
-
841
- #### `#failure_as`
842
-
843
- Substitutes an error `Try::Failure` with a new error, if the monad is instance of `Try::Success`, returns
844
- the monad unchanged.
845
-
846
- ```ruby
847
- divide(10, 2).failure_as(StandardError.new("Error")) # Try::Success[Integer](@value=5)
848
- divide(10, 0).failure_as(StandardError.new("Error")) # Try::Failure[Integer](@failure=#<StandardError: Error>)
849
- ```
850
-
851
- #### `#recover`
852
-
853
- Converts `Failure` into a `Success` with a provided value. If the monad is an instance of `Success`, returns itself.
854
- Basically, recovers from an error with a successful value.
855
-
856
- ```ruby
857
- divide(10, 2).recover(0) # Try::Success[Integer](value = 5)
858
- divide(10, 0).recover(0) # Try::Success[Integer](value = 5)
859
- ```
860
-
861
- #### `#recover_on`
862
-
863
- Converts `Failure` into a `Success` with a provided value if an error is an instance of a subtype of provided class. If the monad is an instance of `Success`, returns itself.
864
- Allows to recover from specific errors.
865
-
866
- ```ruby
867
- class CustomError1 < StandardError
868
- end
869
-
870
- class CustomError2 < StandardError
871
- end
872
-
873
- failure1 = Mayak::Monads::Try::Failure[String].new(CustomError1.new("Error1"))
874
- failure2 = Mayak::Monads::Try::Failure[String].new(CustomError2.new("Error2"))
875
-
876
- failure1.recover_on(CustomError1) { |error| error.message } # Try::Success[String](@value="Error1")
877
- failure2.recover_on(CustomError1) { |error| error.message } # Try::Failure[String](@failure=#<CustomError2: Error2>)
878
- ```
879
-
880
- Note, that an error passed in the block is not down casted. So from sorbet perspective it still will be
881
- a `StandardError`.
882
-
883
- ```ruby
884
- failure1.recover_on(CustomError1) { |error|
885
- T.reveal_type(error)
886
- "Error"
887
- }
888
- # Revealed type: StandardError
889
- ```
890
-
891
- #### `#recover_with`
892
-
893
- Alias for `#flat_map_failure`.
894
-
895
- #### `.sequence`
896
- `Try.sequence` takes an array of `Try`s and transform it into a `Try` of an array.
897
- If all elements of the array is `Success`, then the method returns `Try::Sucess` containing array of values,
898
- otherwise the method returns `Try::Failure` containing first error.
899
-
900
- ```ruby
901
- include Mayak::Monads
902
-
903
- values = [
904
- Try::Success[Integer].new(1),
905
- Try::Success[Integer].new(2),
906
- Try::Success[Integer].new(3)
907
- ]
908
- Try.sequence(values) # Try::Success[T::Array[Integer]](@value=[1, 2, 3])
909
-
910
- values = [
911
- Try::Success[Integer].new(1),
912
- Try::Failure[Integer].new(ArgumentError.new("Error1")),
913
- Try::Failure[Integer].new(StandardError.new("Error2"))
914
- ]
915
- Try.sequence(values) # Try::Failure[Integer](@failure=#<ArgumentError: Error1>)
916
- ```
917
-
918
- #### `.check`
919
-
920
- Receives a value, an error, and block returning a boolean value. If the block returns true,
921
- the method returns the value wrapped in `Try::Success`, otherwise it returns `Try::Failure` containing the error:
922
-
923
- ```ruby
924
- error = StandardError.new("Error")
925
- Try.check(10, error) { 20 > 10 } # Try::Success[Integer](@failure=10)
926
- Try.check(20, error) { 10 > 20 } # Try::Failure[Integer](@failure=#<StandardError: Error>)
927
- ```
928
-
929
- #### `.guard`
930
-
931
- Receives a block returning a boolean value, and an error. If the block returns true,
932
- the method returns `Try::Success` containing nil, otherwise it returns `Try::Failure` containing an error:
933
-
934
- ```ruby
935
- error = StandardError.new("Error")
936
- Maybe.guard(error) { 20 > 10 } # Try::Success[NilClass](@failure=nil)
937
- Maybe.guard(error) { 10 > 20 } # Try::Failure[NilClass](@failure=#<StandardError: Error>)
938
- ```
939
-
940
- This method may be useful in do-notations when you need need to check some invariant and perform
941
- an early return if it doesn't hold.
942
-
943
- ### Do-notation
944
-
945
- `Try` monad supports `do-notation` just like `Maybe` monad. Check do-notation chapter of [Maybe](#maybe) for motivation.
946
- Do-notation for `Try` is quite similar for `Maybe`'s do-notation, albeit there are some differences in syntax and semantics.
947
- Do-notation scope is created via method `for_try`, a monad is unwrapped via `do_try!`.
948
-
949
- ```ruby
950
- result = for_try {
951
- first = do_try! Try::Success[Integer].new(10)
952
- second = do_try! Try::Success[Integer].new(20)
953
- first + second
954
- }
955
- result # Try::Success[Integer](@value=30)
956
- ```
957
-
958
- When an instance of `Try::Failure` is unwrapped via `do_try!` the whole computation returns this monad.
959
-
960
- ```ruby
961
- result = for_try {
962
- first = do_try! Try::Success[Integer].new(10)
963
- second = do_try! Try::Failure[Integer].new(StandardError.new("Error"))
964
- third = do_try! Try::Success[Integer].new(20)
965
- first + second + third
966
- }
967
- result # Try::Failure[Integer](@failure=#<StandardError: Error>)
968
- ```
969
-
970
- Methods `check_try!` and `guard_try!` to perform early returns.
971
-
972
- ```ruby
973
- sig { params(a: Integer, b: Integer).returns(Try[Float]) }
974
- def compute(a, b)
975
- argument_error = ArgumentError.new("Argument is less than zero")
976
- for_try {
977
- guard_try! (argument_error) { a < 0 || b < 0 }
978
- first = do_try! Try { a / b }
979
- second = do_try! Try { Math.log(a, b) }
980
- result = first + second
981
- check_try!(result, StandardError.new("Number is too big")) { result < 100 }
982
- }
983
- end
984
- ```
985
-
986
- Do-notation for `Try` is fully typed as well.
987
-
988
- ```ruby
989
- for_try {
990
- value = do_try! Try { 10 }
991
- T.reveal_type(value)
992
- value
993
- }
994
- # > Revealed type: Integer
995
-
996
- result = for_try {
997
- value = do_try! Try { 10 }
998
- value
999
- }
1000
- T.reveal_type(result)
1001
- # > Revealed type: Integer
1002
- ```
1003
-
1004
- ## Result
1005
-
1006
- `Result` monad represents result of a computation that can succeed with an arbitrary value, or fail with an arbitrary error.
1007
- As `Try`, `Result` has two subtypes: `Failure` which contains an error and represents failure case, and `Success`, which contains
1008
- a success value. The difference between `Result` and `Try`, is that `Result` has arbitrary error type, while `Try` has it fixed to `StandardError`.
1009
-
1010
-
1011
- ### Initialization
1012
-
1013
- The primary way to create an instance of `Try` is to use constructor method `#Try` from `Mayak::Monads::Try::Mixin`.
1014
- This method receives a block that may raise exceptions. If an exception has been raised inside the block,
1015
- the method will return instance of `Try::Failure` containing an error. Otherwise the method will return result of the block
1016
- wrapped into `Try::Success`
1017
-
1018
- `Result` can be created via primary constructors of `Result::Success` and `Result::Failure`:
1019
-
1020
- ```ruby
1021
- include Mayak::Monads
1022
-
1023
- Result::Success[String, Integer].new(10)
1024
- Result::Failure[String, Integer].new("Error")
1025
- ```
1026
-
1027
- ### Unpacking
1028
-
1029
- Accessing values of `Result` performed in the same as for `Try`.
1030
-
1031
- ```ruby
1032
- success = Result::Success[String, Integer].new(10)
1033
- failure = Result::Failure[String, Integer].new("Error")
1034
- success.success? # true
1035
- success.failure? # false
1036
-
1037
- failure.success? # false
1038
- failure.failure? # true
1039
- ```
1040
-
1041
- Successful and failure values can be accessed via `#success` and `#failure` values. These methods
1042
- are defined on `Result::Success`, and `Result::Failure` subtypes respectively, so in order to access
1043
- them value of type `Result` should be downcasted first:
1044
-
1045
- ```ruby
1046
- result = Result::Success[String, Integer].new(10)
1047
- value = case result
1048
- when Result::Success
1049
- try.success
1050
- else
1051
- nil
1052
- end
1053
- value # 10
1054
-
1055
- result = Result::Failure[String, Integer].new("Error")
1056
- value = case result
1057
- when Result::Failure
1058
- result.failure
1059
- when
1060
- nil
1061
- end
1062
- value # "Error"
1063
- ```
1064
-
1065
- Success and failure values can be retrieved via methods `#success_or` and `#failure_or` as well. These methods
1066
- receives fallback values:
1067
-
1068
- ```ruby
1069
- Result::Success[String, Integer].new(10).success_or(0) # 10
1070
- Result::Success[String, Integer].new(10).failure_or("Error") # "Error"
1071
-
1072
- Result::Failure[String, Integer].new("Error").success_or(0) # 0
1073
- Result::Failure[String, Integer].new("Error").failure_or("Another Error") # "Error"
1074
- ```
1075
-
1076
- ### Methods
1077
-
1078
- #### `#map`
1079
-
1080
- The same as `fmap` in a dry-monads `Result`. Allows to modify the value with a block if it's present.
1081
-
1082
- ```ruby
1083
- sig { returns(Result[String, Integer]) }
1084
- def success
1085
- Result::Success[String, Integer].new(10)
1086
- end
1087
-
1088
- sig { returns(Result[String, Integer]) }
1089
- def failure
1090
- Result::Failure[String, Integer].new("Error")
1091
- end
1092
-
1093
- success.map { |a| a + 20 } # Result::Success[Integer](value = 20)
1094
- failure.map { |a| a + 20 } # Result::Failure[Integer](error=#<StandardError: Error>)
1095
- ```
1096
-
1097
- #### `#flat_map`
1098
-
1099
- The same as `bind` in a dry-monads `Result`. Allows to modify the value with a block that returns another `Result`
1100
- if it's a `Result::Success`, otherwise returns `Result::Failure`. If the block returns `Result::Failure`, the whole computation returns `Result::Failure`.
1101
-
1102
- ```ruby
1103
- sig { params(a: Integer, b: Integer).returns(Mayak::Monads::Result[String, Integer]) }
1104
- def divide(a, b)
1105
- if a == 0
1106
- Result::Failure[String, Integer].new("Division by zero")
1107
- else
1108
- Result::Success[String, Integer].new(a / b)
1109
- end
1110
- end
1111
-
1112
- divide(20, 2).flat_map { |a| divide(a, 2) } # Result::Success[String, Integer](value = 5)
1113
- divide(20, 2).flat_map { |a| divide(a, 0) } # Result::Failure[String, Integer](@failure="Division by zero")
1114
- divide(20, 0).flat_map { |a| divide(a, 2) } # Result::Failure[String, Integer](@failure="Division by zero")
1115
- ```
1116
-
1117
- #### `#filter_or`
1118
-
1119
- Receives a block the returns a boolean value and checks the underlying value with the block when a monad is `Result::Success`.
1120
- Returns `Result::Failure` with provided error if the block called on the value returns `false`, and returns `self` if the block returns `true`.
1121
- Returns self if the original monad is `Result::Failure`.
1122
-
1123
- ```ruby
1124
- divide(10, 2).filter_or("Above 5") { |value| value <= 5 } # Result::Success[String, Integer](value = 5)
1125
- divide(20, 2).filter_or("Above 5") { |value| value <= 5 } # Result::Failure[String, Integer](@failure="Above 5")
1126
- divide(10, 0).filter_or("Above 5") { |value| value <= 5 } # Result::Failure[String, Integer](@failure="Division by zero")
1127
- ```
1128
-
1129
- #### `#success?`
1130
-
1131
- Returns true if a `Result` is a `Result::Success` and false if it's a `Result::Failure`.
1132
-
1133
- ```ruby
1134
- divide(20, 2).success? # true
1135
- divide(20, 0).success? # false
1136
- ```
1137
-
1138
- #### `#failure?`
1139
-
1140
- Returns true if a `Result` is a `Result::Failure` and false if it's a `Result::Success`.
1141
-
1142
- ```ruby
1143
- divide(20, 2).failure? # false
1144
- divide(20, 0).failure? # true
1145
- ```
1146
-
1147
- #### `#success_or`
1148
-
1149
- Unpack a `Result` and returns its success value if it's a `Result::Success`, or returns provided fallback value if it's a `Result::Failure`.
1150
-
1151
- ```ruby
1152
- divide(20, 2).value_or(0) # 10
1153
- divide(20, 0).value_or(0) # 0
1154
- ```
1155
-
1156
- #### `#failure_or`
1157
-
1158
- Unpack a `Result` and returns its error if it's a `Result::Failure`,
1159
- or returns provided fallback error of `StandardError` subtype if it's a `Success`.
1160
-
1161
- ```ruby
1162
- divide(20, 2).failure_or("Error") # "Error"
1163
- divide(20, 0).failure_or("Error") # "Division by zero"
1164
- ```
1165
-
1166
- #### `#flip`
1167
-
1168
- Flips failure and success channels, converting `Result[A, B]` into `Result[B, A]`.
1169
- Basically, transforms `Success` into `Failure` and vice versa.
1170
-
1171
-
1172
- ```ruby
1173
- divide(10, 2).flip # Result::Failure[Integer, String](value = 5)
1174
- divide(10, 0).flip # Result::Success[Integer, String](value = "Division by zero")
1175
- ```
1176
-
1177
- #### `#either`
1178
-
1179
- Receives two functions: one from an error to a result
1180
- value (failure function), and another one from successful value to a result value (success function).
1181
- If a `Result` is an instance of `Result::Success`, applies success function to a success value,
1182
- otherwise applies error function to an error. Note that both functions must have the same return type.
1183
-
1184
- ```ruby
1185
- divide(10, 2).either(
1186
- -> (error) { "Error occurred: `#{error}`" },
1187
- -> (value) { value.to_s }
1188
- ) # 5
1189
-
1190
- divide(10, 0).either(
1191
- -> (error) { "Error occurred: #{error}" },
1192
- -> (value) { value.to_s }
1193
- ) # Error occurred: `Division by zero`
1194
- ```
1195
-
1196
- #### `#tee`
1197
-
1198
- If a `Result` is an instance of `Result::Success`, runs a block with a value of `Result::Success` passed, and returns the monad itself
1199
- unchanged. Doesn't do anything if the monad is an instance of `Result::Failure`.
1200
-
1201
- ```ruby
1202
- divide(10, 2).tee { |a| puts a }
1203
- # returns: Result::Success[String, Integer](value=5)
1204
- # console: 5
1205
-
1206
- divide(10, 0).tee { |a| puts a }
1207
- # returns: Result::Failure[String, Integer](@failure="Division by zero")
1208
- ```
1209
-
1210
- #### `#map_failure`
1211
-
1212
- Transforms an error with a block if a monad is `Failure`. Returns itself otherwise. Note that the passed block
1213
- should return an instance of `StandardError`, or an instance of a subtype of the `StandardError`.
1214
-
1215
- ```ruby
1216
- divide(10, 0).map_failure { |error| "Error occurred: `#{error}`" } # Result::Failure[String, Integer](@failure="Error occurred: `Division by zero`")
1217
- divide(10, 2).map_failure { |error| "Error occurred: `#{error}`" } # Result::Success[String, Integer](@value=5)
1218
- ```
1219
-
1220
- #### `#flat_map_failure`
1221
-
1222
- Transforms an error with a block that returns an instance of `Result` if a monad is `Result::Failure`. Returns itself otherwise.
1223
- Allows to recover from error with a computation, that can fail too.
1224
-
1225
- ```ruby
1226
- divide(10, 2).flat_map_failure { |_error|
1227
- Result::Success[String, Integer].new(0)
1228
- } # Result::Success[String, Integer](@value=5)
1229
-
1230
- divide(10, 2).flat_map_failure { |error|
1231
- Result::Failure[String, Integer].new("Error")
1232
- } # Result::Success[String, Integer](@value=2)
1233
-
1234
- divide(10, 0).flat_map_failure { |error|
1235
- Result::Success[String, Integer].new(0)
1236
- } # Result::Success[String, Integer](@value=0)
1237
-
1238
- divide(10, 0).flat_map_failure { |error|
1239
- Result::Success[String, Integer].new("Error")
1240
- } # Result::Failure[String, Integer](@failure="Error")
1241
- ```
1242
-
1243
- #### `#to_task`
1244
-
1245
- Converts a value to task. Receives a block that converts an error into an instance of `StandardError`.
1246
- If a `Result` is an instance of `Result::Success`, it returns succeeded task.
1247
- If it's a `Result::Failure`, it returns failed task with an `Failure`'s error returned by the block.
1248
-
1249
- ```ruby
1250
- task = Mayak::Concurrent::Task.execute { 100 }
1251
-
1252
- task.flat_map { |value|
1253
- divide(value, 10).to_task { |error| StandardError.new(error) }
1254
- }.await! # 10
1255
-
1256
- task.flat_map { |value|
1257
- divide(value, 0).to_task { |error| StandardError.new(error) }
1258
- }.await! # StandardError: Divison by zero
1259
- ```
1260
-
1261
- #### `#to_try`
1262
-
1263
- Converts a `Result` into a `Try`. Receives a block that converts an error into an instance of `StandardError`.
1264
- If the `Result` is a `Result::Success`, returns `Try::Success` with a success value.
1265
- If it's a `Result::Failure`, returns `Try::Failre` with a an error value returned by the block.
1266
-
1267
- ```ruby
1268
- divide(10, 2).to_result { |error| StandardError.new(error) } # Try::Success[Integer](value = 5)
1269
- divide(10, 0).to_result { |error| StandardError.new(error) } # Try::Failure[Integer](error = #<ZeroDivisionError: divided by 0>)
1270
- ```
1271
-
1272
- #### `#to_maybe`
1273
-
1274
- Converts a `Result` value to `Maybe`. When the `Result` instance is a `Result::Success`, returns `Maybe` containing successful value.
1275
- When the `Result` is an instance of `Result::Failure`, returns `None`. Note that during the transformation error is lost.
1276
-
1277
- ```ruby
1278
- divide(10, 2).to_maybe # Some[Integer](value = 5)
1279
- divide(10, 0).to_maybe # None[Integer]
1280
- ```
1281
-
1282
- #### `#as`
1283
-
1284
- Substitutes successful value in `Result::Success` with a new value, if the monad is instance of `Result::Failure`, returns
1285
- the monad unchanged.
1286
-
1287
- ```ruby
1288
- divide(10, 2).as("Success") # Result::Success[String, String](@value="Success")
1289
- divide(10, 0).as("Success") # Result::Failure[String, String](@failure="Division by zero")
1290
- ```
1291
-
1292
- #### `#failure_as`
1293
-
1294
- Substitutes an error `Result::Failure` with a new error, if the monad is instance of `Try::Success`, returns
1295
- the monad unchanged.
1296
-
1297
- ```ruby
1298
- divide(10, 2).failure_as(0) # Result::Success[Integer, Integer](@value=5)
1299
- divide(10, 0).failure_as(0) # Result::Failure[Integer, Integer](@failure=0)
1300
- ```
1301
-
1302
- #### `#recover`
1303
-
1304
- Converts `Result::Failure` into a `Result::Success` with a provided value. If the monad is an instance of `Result::Success`, returns itself.
1305
- Basically, recovers from an error with a successful value.
1306
-
1307
- ```ruby
1308
- divide(10, 2).recover(0) # Result::Success[String, Integer](value = 5)
1309
- divide(10, 0).recover(0) # Result::Success[Integer](value = 5)
1310
- ```
1311
-
1312
- #### `#recover_with`
1313
-
1314
- Receives a block that converts an error into successful value, and converts `Failure` into a `Success` with the value.
1315
- If the monad is an instance of `Success`, returns itself.Basically, recovers from an error with a successful value.
1316
-
1317
- ```ruby
1318
- divide(10, 2).recover_with { |error|
1319
- if error == "Division by zero"
1320
- 0
1321
- else
1322
- -1
1323
- end
1324
- } # Result::Success[String, Integer](value = 5)
1325
- divide(10, 2).recover_with { |error|
1326
- if error == "Division by zero"
1327
- 0
1328
- else
1329
- -1
1330
- end
1331
- } # Result::Success[Integer](value = 0)
1332
- ```
1333
-
1334
- #### `#recover_with_result`
1335
-
1336
- Alias for `#flat_map_failure`.
1337
-
1338
- #### `.sequence`
1339
- `Result.sequence` takes an array of `Result`s and transform it into a `Result` of an array.
1340
- If all elements of the array is `Result::Success`, then the method returns `Result::Sucess` containing array of values,
1341
- otherwise the method returns `Result::Failure` containing first error.
1342
-
1343
- ```ruby
1344
- include Mayak::Monads
1345
-
1346
- values = [
1347
- Result::Success[String, Integer].new(1),
1348
- Result::Success[String, Integer].new(2),
1349
- Result::Success[String, Integer].new(3)
1350
- ]
1351
- Result.sequence(values) # Result::Success[String, T::Array[Integer]](@value=[1, 2, 3])
1352
-
1353
- values = [
1354
- Result::Success[String, Integer].new(1),
1355
- Result::Failure[String, Integer].new("Error1"),
1356
- Result::Failure[String, Integer].new("Error2")
1357
- ]
1358
- Result.sequence(values) # Result::Failure[String, Integer](@failure="Error")
1359
- ```
1360
-
1361
- ### Do-notation
1362
-
1363
- `Result` monad does not supports do-notation. The fact that `Result` has two type-parameters makes
1364
- it not possible to implement type-safe do-notation in the same fashion as it done for `Maybe` and `Try`.