fear 0.9.0 → 1.2.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 (155) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rubocop.yml +39 -0
  3. data/.github/workflows/spec.yml +42 -0
  4. data/.gitignore +0 -1
  5. data/.rubocop.yml +4 -12
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +40 -0
  8. data/Gemfile +5 -2
  9. data/Gemfile.lock +130 -0
  10. data/LICENSE.txt +1 -1
  11. data/README.md +1293 -97
  12. data/Rakefile +369 -1
  13. data/benchmarks/README.md +1 -0
  14. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  15. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  16. data/benchmarks/factorial.txt +16 -0
  17. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  18. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  19. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  20. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  21. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  22. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  23. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  24. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  25. data/examples/pattern_extracting.rb +17 -0
  26. data/examples/pattern_extracting_ruby2.7.rb +15 -0
  27. data/examples/pattern_matching_binary_tree_set.rb +101 -0
  28. data/examples/pattern_matching_number_in_words.rb +60 -0
  29. data/fear.gemspec +34 -23
  30. data/lib/dry/types/fear.rb +8 -0
  31. data/lib/dry/types/fear/option.rb +125 -0
  32. data/lib/fear.rb +65 -15
  33. data/lib/fear/await.rb +33 -0
  34. data/lib/fear/awaitable.rb +28 -0
  35. data/lib/fear/either.rb +131 -71
  36. data/lib/fear/either_api.rb +23 -0
  37. data/lib/fear/either_pattern_match.rb +53 -0
  38. data/lib/fear/empty_partial_function.rb +38 -0
  39. data/lib/fear/extractor.rb +112 -0
  40. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
  41. data/lib/fear/extractor/any_matcher.rb +17 -0
  42. data/lib/fear/extractor/array_head_matcher.rb +36 -0
  43. data/lib/fear/extractor/array_matcher.rb +40 -0
  44. data/lib/fear/extractor/array_splat_matcher.rb +16 -0
  45. data/lib/fear/extractor/empty_list_matcher.rb +20 -0
  46. data/lib/fear/extractor/extractor_matcher.rb +44 -0
  47. data/lib/fear/extractor/grammar.rb +203 -0
  48. data/lib/fear/extractor/grammar.treetop +129 -0
  49. data/lib/fear/extractor/identifier_matcher.rb +18 -0
  50. data/lib/fear/extractor/matcher.rb +53 -0
  51. data/lib/fear/extractor/matcher/and.rb +38 -0
  52. data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
  53. data/lib/fear/extractor/pattern.rb +58 -0
  54. data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
  55. data/lib/fear/extractor/value_matcher.rb +19 -0
  56. data/lib/fear/extractor_api.rb +35 -0
  57. data/lib/fear/failure.rb +46 -14
  58. data/lib/fear/failure_pattern_match.rb +10 -0
  59. data/lib/fear/for.rb +37 -95
  60. data/lib/fear/for_api.rb +68 -0
  61. data/lib/fear/future.rb +497 -0
  62. data/lib/fear/future_api.rb +21 -0
  63. data/lib/fear/left.rb +19 -2
  64. data/lib/fear/left_pattern_match.rb +11 -0
  65. data/lib/fear/none.rb +67 -3
  66. data/lib/fear/none_pattern_match.rb +14 -0
  67. data/lib/fear/option.rb +120 -56
  68. data/lib/fear/option_api.rb +40 -0
  69. data/lib/fear/option_pattern_match.rb +48 -0
  70. data/lib/fear/partial_function.rb +176 -0
  71. data/lib/fear/partial_function/and_then.rb +50 -0
  72. data/lib/fear/partial_function/any.rb +28 -0
  73. data/lib/fear/partial_function/combined.rb +53 -0
  74. data/lib/fear/partial_function/empty.rb +10 -0
  75. data/lib/fear/partial_function/guard.rb +80 -0
  76. data/lib/fear/partial_function/guard/and.rb +38 -0
  77. data/lib/fear/partial_function/guard/and3.rb +41 -0
  78. data/lib/fear/partial_function/guard/or.rb +38 -0
  79. data/lib/fear/partial_function/lifted.rb +23 -0
  80. data/lib/fear/partial_function/or_else.rb +64 -0
  81. data/lib/fear/partial_function_class.rb +38 -0
  82. data/lib/fear/pattern_match.rb +114 -0
  83. data/lib/fear/pattern_matching_api.rb +137 -0
  84. data/lib/fear/promise.rb +95 -0
  85. data/lib/fear/right.rb +20 -2
  86. data/lib/fear/right_biased.rb +6 -14
  87. data/lib/fear/right_pattern_match.rb +11 -0
  88. data/lib/fear/some.rb +55 -3
  89. data/lib/fear/some_pattern_match.rb +13 -0
  90. data/lib/fear/struct.rb +248 -0
  91. data/lib/fear/success.rb +35 -5
  92. data/lib/fear/success_pattern_match.rb +12 -0
  93. data/lib/fear/try.rb +136 -79
  94. data/lib/fear/try_api.rb +33 -0
  95. data/lib/fear/try_pattern_match.rb +33 -0
  96. data/lib/fear/unit.rb +32 -0
  97. data/lib/fear/utils.rb +39 -14
  98. data/lib/fear/version.rb +4 -1
  99. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  100. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  101. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  102. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  103. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  104. data/spec/fear/awaitable_spec.rb +17 -0
  105. data/spec/fear/done_spec.rb +8 -6
  106. data/spec/fear/either/mixin_spec.rb +17 -0
  107. data/spec/fear/either_pattern_match_spec.rb +37 -0
  108. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  109. data/spec/fear/extractor/array_matcher_spec.rb +230 -0
  110. data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
  111. data/spec/fear/extractor/grammar_array_spec.rb +25 -0
  112. data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
  113. data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
  114. data/spec/fear/extractor/pattern_spec.rb +34 -0
  115. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
  116. data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
  117. data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
  118. data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
  119. data/spec/fear/extractor_api_spec.rb +115 -0
  120. data/spec/fear/extractor_spec.rb +61 -0
  121. data/spec/fear/failure_spec.rb +145 -45
  122. data/spec/fear/for_spec.rb +57 -67
  123. data/spec/fear/future_spec.rb +691 -0
  124. data/spec/fear/guard_spec.rb +103 -0
  125. data/spec/fear/left_spec.rb +112 -46
  126. data/spec/fear/none_spec.rb +114 -16
  127. data/spec/fear/option/mixin_spec.rb +39 -0
  128. data/spec/fear/option_pattern_match_spec.rb +35 -0
  129. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  130. data/spec/fear/option_spec.rb +121 -8
  131. data/spec/fear/partial_function/empty_spec.rb +38 -0
  132. data/spec/fear/partial_function_and_then_spec.rb +147 -0
  133. data/spec/fear/partial_function_composition_spec.rb +82 -0
  134. data/spec/fear/partial_function_or_else_spec.rb +276 -0
  135. data/spec/fear/partial_function_spec.rb +239 -0
  136. data/spec/fear/pattern_match_spec.rb +93 -0
  137. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  138. data/spec/fear/promise_spec.rb +96 -0
  139. data/spec/fear/right_biased/left.rb +29 -32
  140. data/spec/fear/right_biased/right.rb +51 -54
  141. data/spec/fear/right_spec.rb +109 -41
  142. data/spec/fear/some_spec.rb +80 -15
  143. data/spec/fear/success_spec.rb +99 -32
  144. data/spec/fear/try/mixin_spec.rb +19 -0
  145. data/spec/fear/try_pattern_match_spec.rb +37 -0
  146. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  147. data/spec/fear/utils_spec.rb +16 -14
  148. data/spec/spec_helper.rb +13 -7
  149. data/spec/struct_pattern_matching_spec.rb +36 -0
  150. data/spec/struct_spec.rb +226 -0
  151. data/spec/support/dry_types.rb +6 -0
  152. metadata +320 -29
  153. data/.travis.yml +0 -9
  154. data/lib/fear/done.rb +0 -22
  155. data/lib/fear/for/evaluation_context.rb +0 -91
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4f87d9ddc3fd6c7bdd259ed164ca27668c3421f1
4
- data.tar.gz: f1cd2982080e8b8f302be524f55ef18f9406b3e6
2
+ SHA256:
3
+ metadata.gz: 27594ab99bc8a361057d1222bd4e70aa49f5c8535c95f622f338ab17e216cd31
4
+ data.tar.gz: dfbd5982e2da247cdb74edb7a218023195c4806d56af786098e294165bf345ec
5
5
  SHA512:
6
- metadata.gz: 18926b68929c6b68b8e7e3fe1f5352a2998dade76e1860097b470a93e7d236142a84b1cf98a2413b6b093dd412461bc5fa764f10a3753cf932ab0558d8d59d9e
7
- data.tar.gz: 95c5eecb1b37444c63fdf713bcee58306053ba5708b10b4c7f6d4fa4dd53804143bc321cb5b10927297f136951d83573d9924086b6faa0794a239513603b12c3
6
+ metadata.gz: 4c880fdc1a916306cd222bb4d63526aa3b9ab19eadb095df1ea2aef4343e443015b8067628a34a0b436dc5d994f39fd8810b03dbb05adc7d6df975eb1943bee7
7
+ data.tar.gz: f9e9c441b5d208b65cbd678ae38412d404879834e1ca3ac6636140aa9b53bbb6c1ddf88b613c52d74cb6b09c481a0983c5a3e903c19ee5f98ca94161eb541ba9
@@ -0,0 +1,39 @@
1
+ # pulled from repo
2
+ name: "Rubocop"
3
+
4
+ on: push
5
+
6
+ jobs:
7
+ rubocop:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ fail-fast: false
11
+
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v2
15
+
16
+ # If running on a self-hosted runner, check it meets the requirements
17
+ # listed at https://github.com/ruby/setup-ruby#using-self-hosted-runners
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: 2.6
22
+
23
+ # This step is not necessary if you add the gem to your Gemfile
24
+ - name: Install Code Scanning integration
25
+ run: bundle add code-scanning-rubocop --version 0.4.0 --skip-install
26
+
27
+ - name: Install dependencies
28
+ run: bundle install
29
+
30
+ - name: Rubocop run
31
+ run: |
32
+ bash -c "
33
+ bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif
34
+ [[ $? -ne 2 ]]
35
+ "
36
+ - name: Upload Sarif output
37
+ uses: github/codeql-action/upload-sarif@v1
38
+ with:
39
+ sarif_file: rubocop.sarif
@@ -0,0 +1,42 @@
1
+ name: Spec
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '2.5.x', '2.6.x', '2.7.x' ]
11
+ name: ${{ matrix.ruby }}
12
+
13
+ steps:
14
+ - uses: actions/checkout@v1
15
+ - name: Set up ruby
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: Install dependencies
20
+ run: |
21
+ gem install bundler --force --version=2.0.1
22
+ bundler --version
23
+ bundle install --jobs 4 --retry 3
24
+ - name: Test
25
+ run: bundle exec rspec
26
+ - name: Coveralls
27
+ uses: coverallsapp/github-action@v1.1.2
28
+ with:
29
+ github-token: ${{ secrets.GITHUB_TOKEN }}
30
+ flag-name: ruby-${{ matrix.ruby }}
31
+ parallel: true
32
+
33
+
34
+ finish:
35
+ needs: test
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - name: Coveralls Finished
39
+ uses: coverallsapp/github-action@master
40
+ with:
41
+ github-token: ${{ secrets.GITHUB_TOKEN }}
42
+ parallel-finished: true
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
4
  /coverage/
6
5
  /doc/
@@ -1,15 +1,7 @@
1
1
  inherit_gem:
2
- spbtv_code_style: .strict_rubocop.yml
2
+ ruby_coding_standard: .rubocop.yml
3
3
 
4
- Style/MethodName:
5
- Enabled: false
6
-
7
- Style/Documentation:
8
- Enabled: false
9
-
10
- Style/CaseEquality:
11
- Enabled: false
12
-
13
- Metrics/BlockLength:
4
+ AllCops:
5
+ TargetRubyVersion: 2.7
14
6
  Exclude:
15
- - spec/**/*
7
+ - vendor/**/*
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov-lcov"
4
+
5
+ SimpleCov::Formatter::LcovFormatter.config do |c|
6
+ c.report_with_single_file = true
7
+ c.single_report_path = "coverage/lcov.info"
8
+ end
9
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(
10
+ [
11
+ SimpleCov::Formatter::HTMLFormatter,
12
+ SimpleCov::Formatter::LcovFormatter,
13
+ ],
14
+ )
15
+ SimpleCov.start do
16
+ add_filter "spec/"
17
+ end
@@ -1,3 +1,43 @@
1
+ ## 1.2.0
2
+
3
+ * Implement `Fear::Option#zip` and `Fear::Future#zip` with block argument ([@bolshakov][])
4
+ * Drop ruby 2.4.x support, test against ruby 2.7.x ([@bolshakov][])
5
+ * Implement `Fear::Option#filter_map` ([@bolshakov][])
6
+ * Add dry-types extentions to wrap nullable values. ([@bolshakov][])
7
+ * Implement pattern matching for ruby >= 2.7
8
+ * Deprecate `Fear.xcase`
9
+
10
+ ## 1.1.0
11
+
12
+ * Add `Fear::Await.ready` and `Fear::Await.result`. ([@bolshakov][])
13
+ * Add callback versions with pattern matching `Fear::Future#on_success_match`, `#on_failure_match` and `#on_complete_match`. ([@bolshakov][])
14
+ * Implement immutable `Fear::Struct`. ([@bolshakov][])
15
+
16
+ ## 1.0.0
17
+
18
+ * Rename `Fear::Done` to `Fear::Unit` ([@bolshakov][])
19
+ * Don't treat symbols as procs while pattern matching. See [#46](https://github.com/bolshakov/fear/pull/46) for motivation ([@bolshakov][])
20
+ * Revert commit removing `Fear::Future`. Now you can use `Fear.future` again ([@bolshakov][])
21
+ * Signatures of `Try#recover` and `Try#recover_with` changed. No it pattern match against container
22
+ see https://github.com/bolshakov/fear/issues/41 for details . ([@bolshakov][])
23
+ * Add `#xcase` method to extract patterns ([@bolshakov][])
24
+ * Add `Fear.option`, `Fear.some`, `Fear.none`, `Fear.try`, `Fear.left`, `Fear.right`, and `Fear.for` alternatives to
25
+ including mixins. ([@bolshakov][])
26
+
27
+ ## 0.11.0
28
+
29
+ * Implement pattern matching and partial functions. See [README](https://github.com/bolshakov/fear#pattern-matching-api-documentation) ([@bolshakov][])
30
+ * `#to_a` method removed ([@bolshakov][])
31
+ * `For` syntax changed. See [diff](https://github.com/bolshakov/fear/pull/22/files#diff-04c6e90faac2675aa89e2176d2eec7d8) ([@bolshakov][])
32
+ * `Fear::None` is singleton object now and could not be instantiated ([@bolshakov][])
33
+
34
+ You have to change all `Fear::None.new` invocations to `Fear::None`.
35
+
36
+ ## 0.10.0
37
+
38
+ * Test against last supported ruby versions: 2.4.5, 2.5.3, 2.6.1 ([@bolshakov][])
39
+ * You can use `fear` with any `dry-equalizer` version (up to 0.2.1) ([@bolshakov][])
40
+
1
41
  ## 0.9.0
2
42
 
3
43
  * Test against last supported ruby versions: 2.3.7, 2.4.4, 2.5.1 ([@bolshakov][])
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in functional.gemspec
4
6
  gemspec
5
7
 
6
- # gem 'codeclimate-test-reporter', group: :test, require: nil
8
+ gem "irb"
9
+ gem "qo", github: "baweaver/qo"
@@ -0,0 +1,130 @@
1
+ GIT
2
+ remote: https://github.com/baweaver/qo.git
3
+ revision: 8951ce899559118eb60321014b43cf4211730bd0
4
+ specs:
5
+ qo (0.99.0)
6
+ any (= 0.1.0)
7
+
8
+ PATH
9
+ remote: .
10
+ specs:
11
+ fear (1.2.0)
12
+ lru_redux
13
+ treetop
14
+
15
+ GEM
16
+ remote: https://rubygems.org/
17
+ specs:
18
+ any (0.1.0)
19
+ ast (2.4.1)
20
+ benchmark-ips (2.8.3)
21
+ concurrent-ruby (1.1.7)
22
+ diff-lcs (1.4.4)
23
+ docile (1.3.2)
24
+ dry-configurable (0.11.6)
25
+ concurrent-ruby (~> 1.0)
26
+ dry-core (~> 0.4, >= 0.4.7)
27
+ dry-equalizer (~> 0.2)
28
+ dry-container (0.7.2)
29
+ concurrent-ruby (~> 1.0)
30
+ dry-configurable (~> 0.1, >= 0.1.3)
31
+ dry-core (0.4.9)
32
+ concurrent-ruby (~> 1.0)
33
+ dry-equalizer (0.3.0)
34
+ dry-inflector (0.2.0)
35
+ dry-logic (1.0.6)
36
+ concurrent-ruby (~> 1.0)
37
+ dry-core (~> 0.2)
38
+ dry-equalizer (~> 0.2)
39
+ dry-matcher (0.8.0)
40
+ dry-core (>= 0.4.7)
41
+ dry-monads (1.2.0)
42
+ concurrent-ruby (~> 1.0)
43
+ dry-core (~> 0.4, >= 0.4.4)
44
+ dry-equalizer
45
+ dry-types (1.4.0)
46
+ concurrent-ruby (~> 1.0)
47
+ dry-container (~> 0.3)
48
+ dry-core (~> 0.4, >= 0.4.4)
49
+ dry-equalizer (~> 0.3)
50
+ dry-inflector (~> 0.1, >= 0.1.2)
51
+ dry-logic (~> 1.0, >= 1.0.2)
52
+ fear-rspec (0.3.0)
53
+ fear (>= 1.0.0)
54
+ rspec (~> 3.0)
55
+ irb (1.1.0)
56
+ reline (>= 0.0.1)
57
+ lru_redux (1.1.0)
58
+ parallel (1.20.0)
59
+ parser (2.7.2.0)
60
+ ast (~> 2.4.1)
61
+ polyglot (0.3.5)
62
+ rainbow (3.0.0)
63
+ rake (13.0.1)
64
+ regexp_parser (1.8.2)
65
+ reline (0.0.7)
66
+ rexml (3.2.4)
67
+ rspec (3.10.0)
68
+ rspec-core (~> 3.10.0)
69
+ rspec-expectations (~> 3.10.0)
70
+ rspec-mocks (~> 3.10.0)
71
+ rspec-core (3.10.0)
72
+ rspec-support (~> 3.10.0)
73
+ rspec-expectations (3.10.0)
74
+ diff-lcs (>= 1.2.0, < 2.0)
75
+ rspec-support (~> 3.10.0)
76
+ rspec-mocks (3.10.0)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.10.0)
79
+ rspec-support (3.10.0)
80
+ rubocop (1.0.0)
81
+ parallel (~> 1.10)
82
+ parser (>= 2.7.1.5)
83
+ rainbow (>= 2.2.2, < 4.0)
84
+ regexp_parser (>= 1.8)
85
+ rexml
86
+ rubocop-ast (>= 0.6.0)
87
+ ruby-progressbar (~> 1.7)
88
+ unicode-display_width (>= 1.4.0, < 2.0)
89
+ rubocop-ast (1.1.1)
90
+ parser (>= 2.7.1.5)
91
+ rubocop-rspec (1.34.0)
92
+ rubocop (>= 0.60.0)
93
+ ruby-progressbar (1.10.1)
94
+ ruby_coding_standard (0.3.0)
95
+ rubocop (~> 1.0.0)
96
+ simplecov (0.19.1)
97
+ docile (~> 1.1)
98
+ simplecov-html (~> 0.11)
99
+ simplecov-html (0.12.3)
100
+ simplecov-lcov (0.8.0)
101
+ treetop (1.6.11)
102
+ polyglot (~> 0.3)
103
+ unicode-display_width (1.7.0)
104
+ yard (0.9.25)
105
+
106
+ PLATFORMS
107
+ ruby
108
+
109
+ DEPENDENCIES
110
+ benchmark-ips
111
+ bundler
112
+ concurrent-ruby
113
+ dry-matcher
114
+ dry-monads
115
+ dry-types
116
+ fear!
117
+ fear-rspec
118
+ irb
119
+ qo!
120
+ rake (~> 13.0)
121
+ rspec (~> 3.1)
122
+ rubocop (= 1.0.0)
123
+ rubocop-rspec (= 1.34.0)
124
+ ruby_coding_standard
125
+ simplecov
126
+ simplecov-lcov
127
+ yard
128
+
129
+ BUNDLED WITH
130
+ 2.0.2
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2017 Tema Bolshakov
1
+ Copyright (c) 2015-2019 Tema Bolshakov
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Fear
2
- [![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/dbdcfb770918c425e5e4/maintainability)](https://codeclimate.com/github/bolshakov/functional/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/dbdcfb770918c425e5e4/test_coverage)](https://codeclimate.com/github/bolshakov/functional/test_coverage)
5
2
  [![Gem Version](https://badge.fury.io/rb/fear.svg)](https://badge.fury.io/rb/fear)
3
+ ![Specs](https://github.com/bolshakov/fear/workflows/Spec/badge.svg)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/01030620c59e9f40961b/maintainability)](https://codeclimate.com/github/bolshakov/fear/maintainability)
5
+ [![Coverage Status](https://coveralls.io/repos/github/bolshakov/fear/badge.svg?branch=master)](https://coveralls.io/github/bolshakov/fear?branch=master)
6
6
 
7
7
  This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
8
8
  It is highly inspired by scala's implementation.
@@ -25,168 +25,1363 @@ Or install it yourself as:
25
25
 
26
26
  ## Usage
27
27
 
28
- ### Option ([Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
28
+ * [Option](#option-api-documentation)
29
+ * [Try](#try-api-documentation)
30
+ * [Either](#either-api-documentation)
31
+ * [Future](#future-api-documentation)
32
+ * [For composition](#for-composition-api-documentation)
33
+ * [Pattern Matching](#pattern-matching-api-documentation)
34
+
35
+ ### Option ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
29
36
 
30
37
  Represents optional (nullable) values. Instances of `Option` are either an instance of
31
38
  `Some` or the object `None`.
32
39
 
33
- The most idiomatic way to use an `Option` instance is to treat it
34
- as a collection and use `map`, `flat_map`, `select`, or `each`:
40
+ The most idiomatic way to use an `Option` instance is to treat it as a collection
41
+
42
+ ```ruby
43
+ name = Fear.option(params[:name])
44
+ upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
45
+ puts upper.get_or_else('')
46
+ ```
47
+
48
+ This allows for sophisticated chaining of `Option` values without
49
+ having to check for the existence of a value.
50
+
51
+ A less-idiomatic way to use `Option` values is via pattern matching
52
+
53
+ ```ruby
54
+ Fear.option(params[:name]).match do |m|
55
+ m.some { |name| name.strip.upcase }
56
+ m.none { 'No name value' }
57
+ end
58
+ ```
59
+
60
+ or manually checking for non emptiness
61
+
62
+ ```ruby
63
+ name = Fear.option(params[:name])
64
+ if name.empty?
65
+ puts 'No name value'
66
+ else
67
+ puts name.strip.upcase
68
+ end
69
+ ```
70
+
71
+ Alternatively, include `Fear::Option::Mixin` to use `Option()`, `Some()` and `None()` methods:
72
+
73
+ ```ruby
74
+ include Fear::Option::Mixin
75
+
76
+ Option(42) #=> #<Fear::Some get=42>
77
+ Option(nil) #=> #<Fear::None>
78
+
79
+ Some(42) #=> #<Fear::Some get=42>
80
+ Some(nil) #=> #<Fear::Some get=nil>
81
+ None() #=> #<Fear::None>
82
+ ```
83
+
84
+ #### Option#get_or_else
85
+
86
+ Returns the value from this `Some` or evaluates the given default argument if this is a `None`.
87
+
88
+ ```ruby
89
+ Fear.some(42).get_or_else { 24/2 } #=> 42
90
+ Fear.none.get_or_else { 24/2 } #=> 12
91
+
92
+ Fear.some(42).get_or_else(12) #=> 42
93
+ Fear.none.get_or_else(12) #=> 12
94
+ ```
95
+
96
+ #### Option#or_else
97
+
98
+ returns self `Some` or the given alternative if this is a `None`.
99
+
100
+ ```ruby
101
+ Fear.some(42).or_else { Fear.some(21) } #=> Fear.some(42)
102
+ Fear.none.or_else { Fear.some(21) } #=> Fear.some(21)
103
+ Fear.none.or_else { None } #=> None
104
+ ```
105
+
106
+ #### Option#include?
107
+
108
+ Checks if `Option` has an element that is equal (as determined by `==`) to given values.
109
+
110
+ ```ruby
111
+ Fear.some(17).include?(17) #=> true
112
+ Fear.some(17).include?(7) #=> false
113
+ Fear.none.include?(17) #=> false
114
+ ```
115
+
116
+ #### Option#each
117
+
118
+ Performs the given block if this is a `Some`.
119
+
120
+ ```ruby
121
+ Fear.some(17).each { |value| puts value } #=> prints 17
122
+ Fear.none.each { |value| puts value } #=> does nothing
123
+ ```
124
+
125
+ #### Option#map
126
+
127
+ Maps the given block to the value from this `Some` or returns self if this is a `None`
128
+
129
+ ```ruby
130
+ Fear.some(42).map { |v| v/2 } #=> Fear.some(21)
131
+ Fear.none.map { |v| v/2 } #=> None
132
+ ```
133
+
134
+ #### Option#flat_map
135
+
136
+ Returns the given block applied to the value from this `Some` or returns self if this is a `None`
137
+
138
+ ```ruby
139
+ Fear.some(42).flat_map { |v| Fear.some(v/2) } #=> Fear.some(21)
140
+ Fear.none.flat_map { |v| Fear.some(v/2) } #=> None
141
+ ```
142
+
143
+ #### Option#any?
144
+
145
+ Returns `false` if `None` or returns the result of the application of the given predicate to the `Some` value.
146
+
147
+ ```ruby
148
+ Fear.some(12).any? { |v| v > 10 } #=> true
149
+ Fear.some(7).any? { |v| v > 10 } #=> false
150
+ Fear.none.any? { |v| v > 10 } #=> false
151
+ ```
152
+
153
+ #### Option#select
154
+
155
+ Returns self if it is nonempty and applying the predicate to this `Option`'s value returns `true`. Otherwise,
156
+ return `None`.
157
+
158
+ ```ruby
159
+ Fear.some(42).select { |v| v > 40 } #=> Fear.success(21)
160
+ Fear.some(42).select { |v| v < 40 } #=> None
161
+ Fear.none.select { |v| v < 40 } #=> None
162
+ ```
163
+
164
+ #### Option#filter_map
165
+
166
+ Returns a new `Some` of truthy results (everything except `false` or `nil`) of
167
+ running the block or `None` otherwise.
168
+
169
+ ```ruby
170
+ Fear.some(42).filter_map { |v| v/2 if v.even? } #=> Fear.some(21)
171
+ Fear.some(42).filter_map { |v| v/2 if v.odd? } #=> Fear.none
172
+ Fear.some(42).filter_map { |v| false } #=> Fear.none
173
+ Fear.none.filter_map { |v| v/2 } #=> Fear.none
174
+ ```
175
+
176
+ #### Option#reject
177
+
178
+ Returns `Some` if applying the predicate to this `Option`'s value returns `false`. Otherwise, return `None`.
179
+
180
+ ```ruby
181
+ Fear.some(42).reject { |v| v > 40 } #=> None
182
+ Fear.some(42).reject { |v| v < 40 } #=> Fear.some(42)
183
+ Fear.none.reject { |v| v < 40 } #=> None
184
+ ```
185
+
186
+ #### Option#get
187
+
188
+ Not an idiomatic way of using Option at all. Returns values of raise `NoSuchElementError` error if option is empty.
189
+
190
+ #### Option#empty?
191
+
192
+ Returns `true` if the `Option` is `None`, `false` otherwise.
193
+
194
+ ```ruby
195
+ Fear.some(42).empty? #=> false
196
+ Fear.none.empty? #=> true
197
+ ```
198
+
199
+ #### Option#zip
200
+
201
+ Returns a `Fear::Some` formed from this Option and another Option by combining the corresponding elements in a pair.
202
+ If either of the two options is empty, `Fear::None` is returned.
203
+
204
+ ```ruby
205
+ Fear.some("foo").zip(Fear.some("bar")) #=> Fear.some(["foo", "bar"])
206
+ Fear.some("foo").zip(Fear.some("bar")) { |x, y| x + y } #=> Fear.some("foobar")
207
+ Fear.some("foo").zip(Fear.none) #=> Fear.none
208
+ Fear.none.zip(Fear.some("bar")) #=> Fear.none
209
+
210
+ ```
211
+
212
+ @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
213
+
214
+
215
+ ### Try ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try))
216
+
217
+ The `Try` represents a computation that may either result
218
+ in an exception, or return a successfully computed value. Instances of `Try`,
219
+ are either an instance of `Success` or `Failure`.
220
+
221
+ For example, `Try` can be used to perform division on a
222
+ user-defined input, without the need to do explicit
223
+ exception-handling in all of the places that an exception
224
+ might occur.
225
+
226
+ ```ruby
227
+ dividend = Fear.try { Integer(params[:dividend]) }
228
+ divisor = Fear.try { Integer(params[:divisor]) }
229
+ problem = dividend.flat_map { |x| divisor.map { |y| x / y } }
230
+
231
+ problem.match |m|
232
+ m.success do |result|
233
+ puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
234
+ end
235
+
236
+ m.failure(ZeroDivisionError) do
237
+ puts "Division by zero is not allowed"
238
+ end
239
+
240
+ m.failure do |exception|
241
+ puts "You entered something wrong. Try again"
242
+ puts "Info from the exception: #{exception.message}"
243
+ end
244
+ end
245
+ ```
246
+
247
+ An important property of `Try` shown in the above example is its
248
+ ability to _pipeline_, or chain, operations, catching exceptions
249
+ along the way. The `flat_map` and `map` combinators in the above
250
+ example each essentially pass off either their successfully completed
251
+ value, wrapped in the `Success` type for it to be further operated
252
+ upon by the next combinator in the chain, or the exception wrapped
253
+ in the `Failure` type usually to be simply passed on down the chain.
254
+ Combinators such as `recover_with` and `recover` are designed to provide some
255
+ type of default behavior in the case of failure.
256
+
257
+ *NOTE*: Only non-fatal exceptions are caught by the combinators on `Try`.
258
+ Serious system errors, on the other hand, will be thrown.
259
+
260
+ Alternatively, include `Fear::Try::Mixin` to use `Try()` method:
261
+
262
+ ```ruby
263
+ include Fear::Try::Mixin
264
+
265
+ Try { 4/0 } #=> #<Fear::Failure exception=...>
266
+ Try { 4/2 } #=> #<Fear::Success value=2>
267
+ ```
268
+
269
+ #### Try#get_or_else
270
+
271
+ Returns the value from this `Success` or evaluates the given default argument if this is a `Failure`.
272
+
273
+ ```ruby
274
+ Fear.success(42).get_or_else { 24/2 } #=> 42
275
+ Fear.failure(ArgumentError.new).get_or_else { 24/2 } #=> 12
276
+ ```
277
+
278
+ #### Try#include?
279
+
280
+ Returns `true` if it has an element that is equal given values, `false` otherwise.
281
+
282
+ ```ruby
283
+ Fear.success(17).include?(17) #=> true
284
+ Fear.success(17).include?(7) #=> false
285
+ Fear.failure(ArgumentError.new).include?(17) #=> false
286
+ ```
287
+
288
+ #### Try#each
289
+
290
+ Performs the given block if this is a `Success`. If block raise an error,
291
+ then this method may raise an exception.
292
+
293
+ ```ruby
294
+ Fear.success(17).each { |value| puts value } #=> prints 17
295
+ Fear.failure(ArgumentError.new).each { |value| puts value } #=> does nothing
296
+ ```
297
+
298
+ #### Try#map
299
+
300
+ Maps the given block to the value from this `Success` or returns self if this is a `Failure`.
301
+
302
+ ```ruby
303
+ Fear.success(42).map { |v| v/2 } #=> Fear.success(21)
304
+ Fear.failure(ArgumentError.new).map { |v| v/2 } #=> Fear.failure(ArgumentError.new)
305
+ ```
306
+
307
+ #### Try#flat_map
308
+
309
+ Returns the given block applied to the value from this `Success`or returns self if this is a `Failure`.
310
+
311
+ ```ruby
312
+ Fear.success(42).flat_map { |v| Fear.success(v/2) } #=> Fear.success(21)
313
+ Fear.failure(ArgumentError.new).flat_map { |v| Fear.success(v/2) } #=> Fear.failure(ArgumentError.new)
314
+ ```
315
+
316
+ #### Try#to_option
317
+
318
+ Returns an `Some` containing the `Success` value or a `None` if this is a `Failure`.
319
+
320
+ ```ruby
321
+ Fear.success(42).to_option #=> Fear.some(21)
322
+ Fear.failure(ArgumentError.new).to_option #=> None
323
+ ```
324
+
325
+ #### Try#any?
326
+
327
+ Returns `false` if `Failure` or returns the result of the application of the given predicate to the `Success` value.
328
+
329
+ ```ruby
330
+ Fear.success(12).any? { |v| v > 10 } #=> true
331
+ Fear.success(7).any? { |v| v > 10 } #=> false
332
+ Fear.failure(ArgumentError.new).any? { |v| v > 10 } #=> false
333
+ ```
334
+
335
+ #### Try#success? and Try#failure?
336
+
337
+
338
+ ```ruby
339
+ Fear.success(12).success? #=> true
340
+ Fear.success(12).failure? #=> true
341
+
342
+ Fear.failure(ArgumentError.new).success? #=> false
343
+ Fear.failure(ArgumentError.new).failure? #=> true
344
+ ```
345
+
346
+ #### Try#get
347
+
348
+ Returns the value from this `Success` or raise the exception if this is a `Failure`.
349
+
350
+ ```ruby
351
+ Fear.success(42).get #=> 42
352
+ Fear.failure(ArgumentError.new).get #=> ArgumentError: ArgumentError
353
+ ```
354
+
355
+ #### Try#or_else
356
+
357
+ Returns self `Try` if it's a `Success` or the given alternative if this is a `Failure`.
358
+
359
+ ```ruby
360
+ Fear.success(42).or_else { Fear.success(-1) } #=> Fear.success(42)
361
+ Fear.failure(ArgumentError.new).or_else { Fear.success(-1) } #=> Fear.success(-1)
362
+ Fear.failure(ArgumentError.new).or_else { Fear.try { 1/0 } } #=> Fear.failure(ZeroDivisionError.new('divided by 0'))
363
+ ```
364
+
365
+ #### Try#flatten
366
+
367
+ Transforms a nested `Try`, ie, a `Success` of `Success`, into an un-nested `Try`, ie, a `Success`.
368
+
369
+ ```ruby
370
+ Fear.success(42).flatten #=> Fear.success(42)
371
+ Fear.success(Fear.success(42)).flatten #=> Fear.success(42)
372
+ Fear.success(Fear.failure(ArgumentError.new)).flatten #=> Fear.failure(ArgumentError.new)
373
+ Fear.failure(ArgumentError.new).flatten { -1 } #=> Fear.failure(ArgumentError.new)
374
+ ```
375
+
376
+ #### Try#select
377
+
378
+ Converts this to a `Failure` if the predicate is not satisfied.
379
+
380
+ ```ruby
381
+ Fear.success(42).select { |v| v > 40 }
382
+ #=> Fear.success(21)
383
+ Fear.success(42).select { |v| v < 40 }
384
+ #=> Fear.failure(Fear::NoSuchElementError.new("Predicate does not hold for 42"))
385
+ Fear.failure(ArgumentError.new).select { |v| v < 40 }
386
+ #=> Fear.failure(ArgumentError.new)
387
+ ```
388
+
389
+ #### Recovering from errors
390
+
391
+ There are two ways to recover from the error. `Try#recover_with` method is like `flat_map` for the exception. And
392
+ you can pattern match against the error!
393
+
394
+ ```ruby
395
+ Fear.success(42).recover_with do |m|
396
+ m.case(ZeroDivisionError) { Fear.success(0) }
397
+ end #=> Fear.success(42)
398
+
399
+ Fear.failure(ArgumentError.new).recover_with do |m|
400
+ m.case(ZeroDivisionError) { Fear.success(0) }
401
+ m.case(ArgumentError) { |error| Fear.success(error.class.name) }
402
+ end #=> Fear.success('ArgumentError')
403
+ ```
404
+
405
+ If the block raises error, this new error returned as an result
406
+
407
+ ```ruby
408
+ Fear.failure(ArgumentError.new).recover_with do
409
+ raise
410
+ end #=> Fear.failure(RuntimeError)
411
+ ```
412
+
413
+ The second possibility for recovery is `Try#recover` method. It is like `map` for the exception. And it's also heavely
414
+ relies on pattern matching.
415
+
416
+ ```ruby
417
+ Fear.success(42).recover do |m|
418
+ m.case(&:message)
419
+ end #=> Fear.success(42)
420
+
421
+ Fear.failure(ArgumentError.new).recover do |m|
422
+ m.case(ZeroDivisionError) { 0 }
423
+ m.case(&:message)
424
+ end #=> Fear.success('ArgumentError')
425
+ ```
426
+
427
+ If the block raises an error, this new error returned as an result
428
+
429
+ ```ruby
430
+ Fear.failure(ArgumentError.new).recover do |m|
431
+ raise
432
+ end #=> Fear.failure(RuntimeError)
433
+ ```
434
+
435
+ #### Try#to_either
436
+
437
+ Returns `Left` with exception if this is a `Failure`, otherwise returns `Right` with `Success` value.
438
+
439
+ ```ruby
440
+ Fear.success(42).to_either #=> Fear.right(42)
441
+ Fear.failure(ArgumentError.new).to_either #=> Fear.left(ArgumentError.new)
442
+ ```
443
+
444
+ ### Either ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
445
+
446
+ Represents a value of one of two possible types (a disjoint union.)
447
+ An instance of `Either` is either an instance of `Left` or `Right`.
448
+
449
+ A common use of `Either` is as an alternative to `Option` for dealing
450
+ with possible missing values. In this usage, `None` is replaced
451
+ with a `Left` which can contain useful information.
452
+ `Right` takes the place of `Some`. Convention dictates
453
+ that `Left` is used for failure and `Right` is used for Right.
454
+
455
+ For example, you could use `Either<String, Fixnum>` to `#select_or_else` whether a
456
+ received input is a +String+ or an +Fixnum+.
457
+
458
+ ```ruby
459
+ in = Readline.readline('Type Either a string or an Int: ', true)
460
+ result = begin
461
+ Fear.right(Integer(in))
462
+ rescue ArgumentError
463
+ Fear.left(in)
464
+ end
465
+
466
+ result.match do |m|
467
+ m.right do |x|
468
+ "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
469
+ end
470
+
471
+ m.left do |x|
472
+ "You passed me the String: #{x}"
473
+ end
474
+ end
475
+ ```
476
+
477
+ Either is right-biased, which means that `Right` is assumed to be the default case to
478
+ operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
479
+ unchanged.
480
+
481
+ Alternatively, include `Fear::Either::Mixin` to use `Left()`, and `Right()` methods:
482
+
483
+ ```ruby
484
+ include Fear::Either::Mixin
485
+
486
+ Left(42) #=> #<Fear::Left value=42>
487
+ Right(42) #=> #<Fear::Right value=42>
488
+ ```
489
+
490
+ #### Either#get_or_else
491
+
492
+ Returns the value from this `Right` or evaluates the given default argument if this is a `Left`.
493
+
494
+ ```ruby
495
+ Fear.right(42).get_or_else { 24/2 } #=> 42
496
+ Fear.left('undefined').get_or_else { 24/2 } #=> 12
497
+
498
+ Fear.right(42).get_or_else(12) #=> 42
499
+ Fear.left('undefined').get_or_else(12) #=> 12
500
+ ```
501
+
502
+ #### Either#or_else
503
+
504
+ Returns self `Right` or the given alternative if this is a `Left`.
505
+
506
+ ```ruby
507
+ Fear.right(42).or_else { Fear.right(21) } #=> Fear.right(42)
508
+ Fear.left('unknown').or_else { Fear.right(21) } #=> Fear.right(21)
509
+ Fear.left('unknown').or_else { Fear.left('empty') } #=> Fear.left('empty')
510
+ ```
511
+
512
+ #### Either#include?
513
+
514
+ Returns `true` if `Right` has an element that is equal to given value, `false` otherwise.
515
+
516
+ ```ruby
517
+ Fear.right(17).include?(17) #=> true
518
+ Fear.right(17).include?(7) #=> false
519
+ Fear.left('undefined').include?(17) #=> false
520
+ ```
521
+
522
+ #### Either#each
523
+
524
+ Performs the given block if this is a `Right`.
525
+
526
+ ```ruby
527
+ Fear.right(17).each { |value| puts value } #=> prints 17
528
+ Fear.left('undefined').each { |value| puts value } #=> does nothing
529
+ ```
530
+
531
+ #### Either#map
532
+
533
+ Maps the given block to the value from this `Right` or returns self if this is a `Left`.
534
+
535
+ ```ruby
536
+ Fear.right(42).map { |v| v/2 } #=> Fear.right(21)
537
+ Fear.left('undefined').map { |v| v/2 } #=> Fear.left('undefined')
538
+ ```
539
+
540
+ #### Either#flat_map
541
+
542
+ Returns the given block applied to the value from this `Right` or returns self if this is a `Left`.
543
+
544
+ ```ruby
545
+ Fear.right(42).flat_map { |v| Fear.right(v/2) } #=> Fear.right(21)
546
+ Fear.left('undefined').flat_map { |v| Fear.right(v/2) } #=> Fear.left('undefined')
547
+ ```
548
+
549
+ #### Either#to_option
550
+
551
+ Returns an `Some` containing the `Right` value or a `None` if this is a `Left`.
552
+
553
+ ```ruby
554
+ Fear.right(42).to_option #=> Fear.some(21)
555
+ Fear.left('undefined').to_option #=> Fear::None
556
+ ```
557
+
558
+ #### Either#any?
559
+
560
+ Returns `false` if `Left` or returns the result of the application of the given predicate to the `Right` value.
561
+
562
+ ```ruby
563
+ Fear.right(12).any? { |v| v > 10 } #=> true
564
+ Fear.right(7).any? { |v| v > 10 } #=> false
565
+ Fear.left('undefined').any? { |v| v > 10 } #=> false
566
+ ```
567
+
568
+ #### Either#right?, Either#success?
569
+
570
+ Returns `true` if this is a `Right`, `false` otherwise.
571
+
572
+ ```ruby
573
+ Fear.right(42).right? #=> true
574
+ Fear.left('err').right? #=> false
575
+ ```
576
+
577
+ #### Either#left?, Either#failure?
578
+
579
+ Returns `true` if this is a `Left`, `false` otherwise.
580
+
581
+ ```ruby
582
+ Fear.right(42).left? #=> false
583
+ Fear.left('err').left? #=> true
584
+ ```
585
+
586
+ #### Either#select_or_else
587
+
588
+ Returns `Left` of the default if the given predicate does not hold for the right value, otherwise,
589
+ returns `Right`.
590
+
591
+ ```ruby
592
+ Fear.right(12).select_or_else(-1, &:even?) #=> Fear.right(12)
593
+ Fear.right(7).select_or_else(-1, &:even?) #=> Fear.left(-1)
594
+ Fear.left(12).select_or_else(-1, &:even?) #=> Fear.left(12)
595
+ Fear.left(12).select_or_else(-> { -1 }, &:even?) #=> Fear.left(12)
596
+ ```
597
+
598
+ #### Either#select
599
+
600
+ Returns `Left` of value if the given predicate does not hold for the right value, otherwise, returns `Right`.
601
+
602
+ ```ruby
603
+ Fear.right(12).select(&:even?) #=> Fear.right(12)
604
+ Fear.right(7).select(&:even?) #=> Fear.left(7)
605
+ Fear.left(12).select(&:even?) #=> Fear.left(12)
606
+ Fear.left(7).select(&:even?) #=> Fear.left(7)
607
+ ```
608
+
609
+ #### Either#reject
610
+
611
+ Returns `Left` of value if the given predicate holds for the right value, otherwise, returns `Right`.
612
+
613
+ ```ruby
614
+ Fear.right(12).reject(&:even?) #=> Fear.left(12)
615
+ Fear.right(7).reject(&:even?) #=> Fear.right(7)
616
+ Fear.left(12).reject(&:even?) #=> Fear.left(12)
617
+ Fear.left(7).reject(&:even?) #=> Fear.left(7)
618
+ ```
619
+
620
+ #### Either#swap
621
+
622
+ If this is a `Left`, then return the left value in `Right` or vice versa.
623
+
624
+ ```ruby
625
+ Fear.left('left').swap #=> Fear.right('left')
626
+ Fear.right('right').swap #=> Fear.left('left')
627
+ ```
628
+
629
+ #### Either#reduce
630
+
631
+ Applies `reduce_left` if this is a `Left` or `reduce_right` if this is a `Right`.
632
+
633
+ ```ruby
634
+ result = possibly_failing_operation()
635
+ log(
636
+ result.reduce(
637
+ ->(ex) { "Operation failed with #{ex}" },
638
+ ->(v) { "Operation produced value: #{v}" },
639
+ )
640
+ )
641
+ ```
642
+
643
+ #### Either#join_right
644
+
645
+ Joins an `Either` through `Right`. This method requires that the right side of this `Either` is itself an
646
+ `Either` type. This method, and `join_left`, are analogous to `Option#flatten`
647
+
648
+ ```ruby
649
+ Fear.right(Fear.right(12)).join_right #=> Fear.right(12)
650
+ Fear.right(Fear.left("flower")).join_right #=> Fear.left("flower")
651
+ Fear.left("flower").join_right #=> Fear.left("flower")
652
+ Fear.left(Fear.right("flower")).join_right #=> Fear.left(Fear.right("flower"))
653
+ ```
654
+
655
+ #### Either#join_left
656
+
657
+ Joins an `Either` through `Left`. This method requires that the left side of this `Either` is itself an
658
+ `Either` type. This method, and `join_right`, are analogous to `Option#flatten`
659
+
660
+ ```ruby
661
+ Fear.left(Fear.right("flower")).join_left #=> Fear.right("flower")
662
+ Fear.left(Fear.left(12)).join_left #=> Fear.left(12)
663
+ Fear.right("daisy").join_left #=> Fear.right("daisy")
664
+ Fear.right(Fear.left("daisy")).join_left #=> Fear.right(Fear.left("daisy"))
665
+ ```
666
+
667
+ ### Future ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Future))
668
+
669
+ Asynchronous computations that yield futures are created
670
+ with the `Fear.future` call
671
+
672
+ ```ruby
673
+ success = "Hello"
674
+ f = Fear.future { success + ' future!' }
675
+ f.on_success do |result|
676
+ puts result
677
+ end
678
+ ```
679
+
680
+ Multiple callbacks may be registered; there is no guarantee
681
+ that they will be executed in a particular order.
682
+
683
+ The future may contain an exception and this means
684
+ that the future failed. Futures obtained through combinators
685
+ have the same error as the future they were obtained from.
686
+
687
+ ```ruby
688
+ f = Fear.future { 5 }
689
+ g = Fear.future { 3 }
690
+
691
+ f.flat_map do |x|
692
+ g.map { |y| x + y }
693
+ end
694
+ ```
695
+
696
+ Futures use [Concurrent::Promise](https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Promise.html#constructor_details)
697
+ under the hood. `Fear.future` accepts optional configuration Hash passed directly to underlying promise. For example,
698
+ run it on custom thread pool.
699
+
700
+ ```ruby
701
+ require 'open-uri'
702
+ pool = Concurrent::FixedThreadPool.new(5)
703
+ future = Fear.future(executor: pool) { open('https://example.com/') }
704
+ future.map(&:read).each do |body|
705
+ puts "#{body}"
706
+ end
707
+
708
+ ```
709
+
710
+ Futures support common monadic operations -- `#map`, `#flat_map`, and `#each`. That's why it's possible to combine them
711
+ using `Fear.for`, It returns the Future containing Success of `5 + 3` eventually.
712
+
713
+ ```ruby
714
+ f = Fear.future { 5 }
715
+ g = Fear.future { 3 }
716
+
717
+ Fear.for(f, g) do |x, y|
718
+ x + y
719
+ end
720
+ ```
721
+
722
+ Future goes with the number of callbacks. You can register several callbacks, but the order of execution isn't guaranteed
35
723
 
36
724
  ```ruby
37
- include Fear::Option::Mixin
725
+ f = Fear.future { ... } # call external service
726
+ f.on_success do |result|
727
+ # handle service response
728
+ end
38
729
 
39
- def normalize_name(name)
40
- Option(name)
41
- .map(&:strip)
42
- .select { |n| n.length != 0 }
43
- .map(&:upcase)
44
- .get_or_else('NONAME')
730
+ f.on_failure do |error|
731
+ # handle exception
45
732
  end
733
+ ```
734
+
735
+ or you can wait for Future completion
46
736
 
47
- normalize_name('robert paulson ') #=> 'ROBERT PAULSON'
48
- normalize_name(nil) #=> 'NONAME'
737
+ ```ruby
738
+ f.on_complete do |result|
739
+ result.match do |m|
740
+ m.success { |value| ... }
741
+ m.failure { |error| ... }
742
+ end
743
+ end
49
744
  ```
50
745
 
51
- This allows for sophisticated chaining of `Option` values without
52
- having to check for the existence of a value.
746
+ In sake of convenience `#on_success` callback aliased as `#each`.
53
747
 
54
- ### Try ([Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try))
748
+ It's possible to get future value directly, but since it may be incomplete, `#value` method returns `Fear::Option`. So,
749
+ there are three possible responses:
55
750
 
56
- The `Try` represents a computation that may either result in an exception,
57
- or return a successfully computed value. Instances of `Try`, are either
58
- an instance of `Success` or `Failure`.
751
+ ```ruby
752
+ future.value #=>
753
+ # Fear::Some<Fear::Success> #=> future completed with value
754
+ # Fear::Some<Fear::Failure> #=> future completed with error
755
+ # Fear::None #=> future not yet completed
756
+ ```
59
757
 
60
- For example, `Try` can be used to perform division on a user-defined input,
61
- without the need to do explicit exception-handling in all of the places
62
- that an exception might occur.
758
+ There is a variety of methods to manipulate with futures.
63
759
 
64
760
  ```ruby
65
- include Fear::Try::Mixin
761
+ Fear.future { open('http://example.com').read }
762
+ .transform(
763
+ ->(value) { ... },
764
+ ->(error) { ... },
765
+ )
66
766
 
67
- dividend = Try { Integer(params[:dividend]) }
68
- divisor = Try { Integer(params[:divisor]) }
767
+ future = Fear.future { 5 }
768
+ future.select(&:odd?) # evaluates to Fear.success(5)
769
+ future.select(&:even?) # evaluates to Fear.error(NoSuchElementError)
770
+ ```
69
771
 
70
- result = dividend.flat_map { |x| divisor.map { |y| x / y } }
772
+ You can zip several asynchronous computations into one future. For you can call two external services and
773
+ then zip the results into one future containing array of both responses:
71
774
 
72
- if result.success?
73
- puts "Result of #{dividend.get} / #{divisor.get} is: #{result.get}"
74
- else
75
- puts "You must've divided by zero or entered something wrong. Try again"
76
- puts "Info from the exception: #{result.exception.message}"
77
- end
78
- ```
775
+ ```ruby
776
+ future1 = Fear.future { call_service1 }
777
+ future1 = Fear.future { call_service2 }
778
+ future1.zip(future2)
779
+ ```
79
780
 
80
- ### Either ([Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Either))
781
+ It returns the same result as `Fear.future { [call_service1, call_service2] }`, but the first version performs
782
+ two simultaneous calls.
81
783
 
82
- Represents a value of one of two possible types (a disjoint union.)
83
- An instance of `Either` is either an instance of `Left` or `Right`.
784
+ There are two ways to recover from failure. `Future#recover` is live `#map` for failures:
84
785
 
85
- A common use of `Either` is as an alternative to `Option` for dealing
86
- with possible missing values. In this usage, `None` is replaced
87
- with a `Left` which can contain useful information.
88
- `Right` takes the place of `Some`. Convention dictates
89
- that `Left` is used for failure and `Right` is used for success.
786
+ ```ruby
787
+ Fear.future { 2 / 0 }.recover do |m|
788
+ m.case(ZeroDivisionError) { 0 }
789
+ end #=> returns new future of Fear.success(0)
790
+ ```
791
+
792
+ If the future resolved to success or recovery matcher did not matched, it returns the future `Fear::Failure`.
90
793
 
91
- For example, you could use `Either<String, Fixnum>` to select whether a
92
- received input is a `String` or an `Fixnum`.
794
+ The second option is `Future#fallback_to` method. It allows to fallback to result of another future in case of failure
93
795
 
94
796
  ```ruby
95
- include Fear::Either::Mixin
797
+ future = Fear.future { fail 'error' }
798
+ fallback = Fear.future { 5 }
799
+ future.fallback_to(fallback) # evaluates to 5
800
+ ```
96
801
 
97
- input = Readline.readline('Type Either a string or an Int: ', true)
98
- result = begin
99
- Right(Integer(input))
100
- rescue ArgumentError
101
- Left(input)
802
+ You can run callbacks in specific order using `#and_then` method:
803
+
804
+ ```ruby
805
+ f = Fear.future { 5 }
806
+ f.and_then do
807
+ fail 'runtime error'
808
+ end.and_then do |m|
809
+ m.success { |value| puts value } # it evaluates this branch
810
+ m.failure { |error| puts error.massage }
102
811
  end
812
+ ```
813
+
814
+ #### Testing future values
815
+
816
+ Sometimes it may be helpful to await for future completion. You can await either future,
817
+ or result. Don't forget to pass timeout in seconds:
103
818
 
104
- puts(
105
- result.reduce(
106
- -> (x) { "You passed me the String: #{x}" },
107
- -> (x) { "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}" }
108
- )
109
- )
110
- ```
111
-
112
- ### For composition
819
+
820
+ ```ruby
821
+ future = Fear.future { 42 }
822
+
823
+ Fear::Await.result(future, 3) #=> 42
824
+
825
+ Fear::Await.ready(future, 3) #=> Fear::Future.successful(42)
826
+ ```
827
+
828
+ ### For composition ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/ForApi))
113
829
 
114
830
  Provides syntactic sugar for composition of multiple monadic operations.
115
831
  It supports two such operations - `flat_map` and `map`. Any class providing them
116
832
  is supported by `For`.
117
833
 
118
834
  ```ruby
119
- include Fear::For::Mixin
835
+ Fear.for(Fear.some(2), Fear.some(3)) do |a, b|
836
+ a * b
837
+ end #=> Fear.some(6)
838
+ ```
839
+
840
+ If one of operands is None, the result is None
841
+
842
+ ```ruby
843
+ Fear.for(Fear.some(2), None) do |a, b|
844
+ a * b
845
+ end #=> None
846
+
847
+ Fear.for(None, Fear.some(2)) do |a, b|
848
+ a * b
849
+ end #=> None
850
+ ```
851
+
852
+ Lets look at first example:
853
+
854
+ ```ruby
855
+ Fear.for(Fear.some(2), None) do |a, b|
856
+ a * b
857
+ end #=> None
858
+ ```
859
+
860
+ it is translated to:
120
861
 
121
- def divide(dividend, divisor)
122
- For(x: dividend, y: divisor) do
123
- x / y
862
+ ```ruby
863
+ Fear.some(2).flat_map do |a|
864
+ Fear.some(3).map do |b|
865
+ a * b
124
866
  end
125
867
  end
868
+ ```
126
869
 
127
- dividend = Try { Integer(params[:dividend]) } #=> Try(4)
128
- divisor = Try { Integer(params[:divisor]) } #=> Try(2)
870
+ It works with arrays as well
871
+
872
+ ```ruby
873
+ Fear.for([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
874
+ #=> [6, 8, 9, 12, 12, 16, 18, 24]
129
875
 
130
- divide(dividend, divisor) #=> Try(2)
131
876
  ```
132
877
 
133
- It would be translated to
878
+ it is translated to:
134
879
 
135
880
  ```ruby
136
- Success(4).flat_map do |x|
137
- Success(2).map do |y|
138
- x / y
881
+ [1, 2].flat_map do |a|
882
+ [2, 3].flat_map do |b|
883
+ [3, 4].map do |c|
884
+ a * b * c
885
+ end
139
886
  end
140
887
  end
141
888
  ```
142
889
 
143
- If one of operands is Failure, the result is Failure
890
+ If you pass lambda as a variable value, it would be evaluated
891
+ only on demand.
892
+
893
+ ```ruby
894
+ Fear.for(proc { None }, proc { raise 'kaboom' } ) do |a, b|
895
+ a * b
896
+ end #=> None
897
+ ```
898
+
899
+ It does not fail since `b` is not evaluated.
900
+ You can refer to previously defined variables from within lambdas.
144
901
 
145
902
  ```ruby
903
+ maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
904
+
905
+ Fear.for(maybe_user, ->(user) { user.birthday }) do |user, birthday|
906
+ "#{user.name} was born on #{birthday}"
907
+ end #=> Fear.some('Paul was born on 1987-06-17')
908
+ ```
909
+
910
+ ### Pattern Matching ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/PatternMatchingApi))
146
911
 
147
- dividend = Try { 42 }
148
- divisor = Try { Integer('ehuton') }
912
+ #### Syntax
149
913
 
150
- divide(dividend, divisor) #=> Failure(<ArgumentError: invalid value for Integer(): "ehuton">)
914
+ To pattern match against a value, use `Fear.match` function, and provide at least one case clause:
915
+
916
+ ```ruby
917
+ x = Random.rand(10)
918
+
919
+ Fear.match(x) do |m|
920
+ m.case(0) { 'zero' }
921
+ m.case(1) { 'one' }
922
+ m.case(2) { 'two' }
923
+ m.else { 'many' }
924
+ end
925
+ ```
926
+
927
+ The `x` above is a random integer from 0 to 10. The last clause `else` is a “catch all” case
928
+ for anything other than `0`, `1`, and `2`. If you want to ensure that an Integer value is passed,
929
+ matching against type available:
930
+
931
+ ```ruby
932
+ Fear.match(x) do |m|
933
+ m.case(Integer, 0) { 'zero' }
934
+ m.case(Integer, 1) { 'one' }
935
+ m.case(Integer, 2) { 'two' }
936
+ m.case(Integer) { 'many' }
937
+ end
151
938
  ```
152
939
 
153
- `For` works with arrays as well
940
+ Providing something other than Integer will raise `Fear::MatchError` error.
941
+
942
+ #### Pattern guards
943
+
944
+ You can use whatever you want as a pattern guard, if it respond to `#===` method to to make cases more specific.
154
945
 
155
946
  ```ruby
156
- For(a: [1, 2], b: [2, 3], c: [3, 4]) do
157
- a * b * c
158
- end #=> [6, 8, 9, 12, 12, 16, 18, 24]
947
+ m.case(20..40) { |m| "#{m} is within range" }
948
+ m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
949
+ m.case(:even?.to_proc) { |x| "#{x} is even" }
950
+ m.case(:odd?.to_proc) { |x| "#{x} is odd" }
159
951
  ```
160
-
161
- would be translated to:
952
+
953
+ It's also possible to create matcher and use it several times:
162
954
 
163
955
  ```ruby
164
- [1, 2].flat_map do |a|
165
- [2, 3].flat_map do |b|
166
- [3, 4].map do |c|
167
- a * b * c
168
- end
956
+ matcher = Fear.matcher do |m|
957
+ m.case(Integer) { |n| "#{n} is a number" }
958
+ m.case(String) { |n| "#{n} is a string" }
959
+ m.else { |n| "#{n} is a #{n.class}" }
960
+ end
961
+
962
+ matcher.(42) #=> "42 is a number"
963
+ matcher.(10..20) #=> "10..20 is a Range"
964
+ ```
965
+
966
+ #### Pattern extraction
967
+
968
+ It's possible to use special syntax to match against an object and extract a variable form this object.
969
+ To perform such extraction, `#xcase` method should be used. The following example should give you a sense
970
+ how extraction works.
971
+
972
+ ```ruby
973
+ matcher = Fear.matcher do |m|
974
+ m.xcase('[1, *tail]') { |tail:| tail }
975
+ end
976
+ ```
977
+
978
+ It matches only on an array starting from `1` integer, and captures its tail:
979
+
980
+ ```ruby
981
+ matcher.([1,2,3]) #=> [2,3]
982
+ matcher.([2,3]) #=> raises MatchError
983
+ ```
984
+
985
+ If you want to match against any value, use `_`
986
+
987
+ ```ruby
988
+ matcher = Fear.matcher do |m|
989
+ m.xcase('[1, _, 3]') { .. }
990
+ end
991
+ ```
992
+
993
+ It matches against `[1, 2, 3]`, `[1, 'foo', 3]`, but not `[1, 2]`. It's also possible to capture several variables
994
+ at the same time. Tho following example describes an array starting from `1`, and captures second and third elements.
995
+
996
+
997
+ ```ruby
998
+ matcher = Fear.matcher do |m|
999
+ m.xcase('[1, second, third]') { |second:, third: |.. }
1000
+ end
1001
+ ```
1002
+
1003
+ Matching on deeper structures is possible as well:
1004
+
1005
+ ```ruby
1006
+ matcher = Fear.matcher do |m|
1007
+ m.xcase('[["status", first_status], 4, *tail]') { |first_status:, tail: |.. }
1008
+ end
1009
+ ```
1010
+
1011
+ If you want to capture variable of specific type, there is a type matcher for that case:
1012
+
1013
+ ```ruby
1014
+ matcher = Fear.matcher do |m|
1015
+ m.xcase('[head : String, 2, *]') { |head: | head }
1016
+ end
1017
+ matcher.(['foo', 2]) #=> 'foo'
1018
+ matcher.(['foo', 3]) #=> MatchError
1019
+ matcher.([1, 2]) #=> MatchError
1020
+ ```
1021
+
1022
+ You can extract variables from more complex objects. Fear packed with extractors for monads and `Date` object:
1023
+
1024
+ ```ruby
1025
+ Fear.matcher do |m|
1026
+ m.xcase('Date(year, 2, 29)', ->(year:) { year < 2000 }) do |year:|
1027
+ "#{year} is a leap year before Millennium"
1028
+ end
1029
+
1030
+ m.xcase('Date(year, 2, 29)') do |year:|
1031
+ "#{year} is a leap year after Millennium"
1032
+ end
1033
+
1034
+ m.case(Date) do |date|
1035
+ "#{date.year} is not a leap year"
1036
+ end
1037
+ end
1038
+ ```
1039
+
1040
+ This matcher extracts values from date object and match against them at the same time
1041
+
1042
+ ```ruby
1043
+ matcher.(Date.new(1996,02,29)) #=> "1996 is a leap year before Millennium"
1044
+ matcher.(Date.new(2004,02,29)) #=> "1996 is a leap year after Millennium"
1045
+ matcher.(Date.new(2003,01,24)) #=> "2003 is not a leap year"
1046
+ ```
1047
+
1048
+ Nothing tricky here. The extractor object takes an object and tries to give back the arguments. It's like
1049
+ constructor, but instead of construction an object, it deconstructs it.
1050
+
1051
+ An argument of an extractor may be also a pattern or even introduce a new variable.
1052
+
1053
+ ```ruby
1054
+ matcher = Fear.matcher do |m|
1055
+ m.xcase('Some([status : Integer, body : String])') do |status:, body:|
1056
+ "#{body.bytesize} bytes received with code #{status}"
1057
+ end
1058
+ end
1059
+
1060
+ matcher.(Fear.some([200, 'hello'])) #=> "5 bytes received with code 200"
1061
+ matcher.(Fear.some(['hello', 200])) #=> MatchError
1062
+ ```
1063
+
1064
+ You can provide extractors for you own classes
1065
+
1066
+ ```ruby
1067
+ Fear.register_extractor(User, Fear.case(User) { |user| [user.id, user.email] }.lift)
1068
+ # is the same as
1069
+ Fear.register_extractor(User, proc do |user|
1070
+ if user.is_a?(User)
1071
+ Fear.some([user.id, user.email])
1072
+ else
1073
+ Fear.none
1074
+ end
1075
+ end)
1076
+ ```
1077
+
1078
+ Now extracting user's id and email is possible:
1079
+
1080
+
1081
+ ```ruby
1082
+ Fear.match(user) do |m|
1083
+ m.xcase('User(id, email)') { |id:, email:| }
1084
+ end
1085
+ ```
1086
+
1087
+ Note, registered extractor should return either array of arguments, or boolean.
1088
+
1089
+ #### Extracting struct
1090
+
1091
+ There is predefined `Struct` extractor:
1092
+
1093
+ ```ruby
1094
+ Envelope = Struct.new(:id, :receiver, :sender, :message)
1095
+
1096
+ Fear.matcher do |m|
1097
+ m.xcase('envelope @ Envelope(id, _, sender, _)') do |id:, sender:, envelope:|
1098
+ acknowledge(id, sender)
1099
+ process(acknowledge)
169
1100
  end
170
1101
  end
171
1102
  ```
172
1103
 
173
- ### Pattern Matching
1104
+ #### How to debug pattern extractors?
1105
+
1106
+ You can build pattern manually and ask for failure reason:
1107
+
1108
+ ```ruby
1109
+ Fear['Some([:err, 444])'].failure_reason(Fear.some([:err, 445]))
1110
+ # =>
1111
+ Expected `445` to match:
1112
+ Some([:err, 444])
1113
+ ~~~~~~~~~~~~^
1114
+ ```
1115
+
1116
+ by the way you can also match against such pattern
1117
+
1118
+ ```ruby
1119
+ Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> false
1120
+ Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> true
1121
+ ```
1122
+
1123
+ #### More examples
1124
+
1125
+ Factorial using pattern matching
1126
+
1127
+ ```ruby
1128
+ factorial = Fear.matcher do |m|
1129
+ m.case(->(n) { n <= 1} ) { 1 }
1130
+ m.else { |n| n * factorial.(n - 1) }
1131
+ end
1132
+
1133
+ factorial.(10) #=> 3628800
1134
+ ```
1135
+
1136
+ Fibonacci number
1137
+
1138
+ ```ruby
1139
+ fibonacci = Fear.matcher do |m|
1140
+ m.case(0) { 0 }
1141
+ m.case(1) { 1 }
1142
+ m.case(->(n) { n > 1}) { |n| fibonacci.(n - 1) + fibonacci.(n - 2) }
1143
+ end
1144
+
1145
+ fibonacci.(10) #=> 55
1146
+ ```
1147
+
1148
+ Binary tree set implemented using pattern matching https://gist.github.com/bolshakov/3c51bbf7be95066d55d6d1ac8c605a1d
1149
+
1150
+ #### Monads pattern matching
1151
+
1152
+ You can use `Option#match`, `Either#match`, and `Try#match` method. It performs matching not
1153
+ only on container itself, but on enclosed value as well.
1154
+
1155
+ Pattern match against an `Option`
1156
+
1157
+ ```ruby
1158
+ Fear.some(42).match do |m|
1159
+ m.some { |x| x * 2 }
1160
+ m.none { 'none' }
1161
+ end #=> 84
1162
+ ```
1163
+
1164
+ pattern match on enclosed value
1165
+
1166
+ ```ruby
1167
+ Fear.some(41).match do |m|
1168
+ m.some(:even?.to_proc) { |x| x / 2 }
1169
+ m.some(:odd?.to_proc, ->(v) { v > 0 }) { |x| x * 2 }
1170
+ m.none { 'none' }
1171
+ end #=> 82
1172
+ ```
1173
+
1174
+ it raises `Fear::MatchError` error if nothing matched. To avoid exception, you can pass `#else` branch
1175
+
1176
+ ```ruby
1177
+ Fear.some(42).match do |m|
1178
+ m.some(:odd?.to_proc) { |x| x * 2 }
1179
+ m.else { 'nothing' }
1180
+ end #=> nothing
1181
+ ```
1182
+
1183
+ Pattern matching works the similar way for `Either` and `Try` monads.
1184
+
1185
+ In sake of performance, you may want to generate pattern matching function and reuse it multiple times:
1186
+
1187
+ ```ruby
1188
+ matcher = Fear::Option.matcher do |m|
1189
+ m.some(42) { 'Yep' }
1190
+ m.some { 'Nope' }
1191
+ m.none { 'Error' }
1192
+ end
1193
+
1194
+ matcher.(Fear.some(42)) #=> 'Yep'
1195
+ matcher.(Fear.some(40)) #=> 'Nope'
1196
+ ```
1197
+
1198
+ #### Under the hood
1199
+
1200
+ Pattern matcher is a combination of partial functions wrapped into nice DSL. Every partial function
1201
+ defined on domain described with a guard.
1202
+
1203
+ ```ruby
1204
+ pf = Fear.case(Integer) { |x| x / 2 }
1205
+ pf.defined_at?(4) #=> true
1206
+ pf.defined_at?('Foo') #=> false
1207
+ pf.call('Foo') #=> raises Fear::MatchError
1208
+ pf.call_or_else('Foo') { 'not a number' } #=> 'not a number'
1209
+ pf.call_or_else(4) { 'not a number' } #=> 2
1210
+ pf.lift.call('Foo') #=> Fear::None
1211
+ pf.lift.call(4) #=> Fear.some(2)
1212
+ ```
1213
+
1214
+ It uses `#===` method under the hood, so you can pass:
174
1215
 
175
- `Option`, `Either`, and `Try` contains enhanced version of `#===` method. It performs matching not
176
- only on container itself, but on enclosed value as well. I'm writing all the options in a one
177
- case statement in sake of simplicity.
1216
+ * Class to check kind of an object.
1217
+ * Lambda to evaluate it against an object.
1218
+ * Any literal, like `4`, `"Foobar"`, `:not_found` etc.
1219
+ * Qo matcher -- `m.case(Qo[name: 'John']) { .... }`
178
1220
 
1221
+ Partial functions may be combined with each other:
1222
+
1223
+ ```ruby
1224
+ is_even = Fear.case(->(arg) { arg % 2 == 0}) { |arg| "#{arg} is even" }
1225
+ is_odd = Fear.case(->(arg) { arg % 2 == 1}) { |arg| "#{arg} is odd" }
1226
+
1227
+ (10..20).map(&is_even.or_else(is_odd))
1228
+
1229
+ to_integer = Fear.case(String, &:to_i)
1230
+ integer_two_times = Fear.case(Integer) { |x| x * 2 }
1231
+
1232
+ two_times = to_integer.and_then(integer_two_times).or_else(integer_two_times)
1233
+ two_times.(4) #=> 8
1234
+ two_times.('42') #=> 84
1235
+ ```
1236
+
1237
+ Since matcher is just a syntactic sugar for partial functions, you can combine matchers with partial
1238
+ functions and each other.
1239
+
179
1240
  ```ruby
180
- case Some(42)
181
- when Some(42) #=> matches
182
- when Some(41) #=> does not match
183
- when Some(Fixnum) #=> matches
184
- when Some(String) #=> does not match
185
- when Some((40..43)) #=> matches
186
- when Some(-> (x) { x > 40 }) #=> matches
1241
+ handle_numbers = Fear.case(Integer, &:itself).and_then(
1242
+ Fear.matcher do |m|
1243
+ m.case(0) { 'zero' }
1244
+ m.case(->(n) { n < 10 }) { 'smaller than ten' }
1245
+ m.case(->(n) { n > 10 }) { 'bigger than ten' }
1246
+ end
1247
+ )
1248
+
1249
+ handle_strings = Fear.case(String, &:itself).and_then(
1250
+ Fear.matcher do |m|
1251
+ m.case('zero') { 0 }
1252
+ m.case('one') { 1 }
1253
+ m.else { 'unexpected' }
1254
+ end
1255
+ )
1256
+
1257
+ handle = handle_numbers.or_else(handle_strings)
1258
+ handle.(0) #=> 'zero'
1259
+ handle.(12) #=> 'bigger than ten'
1260
+ handle.('one') #=> 1
1261
+ ```
1262
+
1263
+ ### Native pattern-matching
1264
+
1265
+ Starting from ruby 2.7 you can use native pattern matching capabilities:
1266
+
1267
+ ```ruby
1268
+ case Fear.some(42)
1269
+ in Fear::Some(x)
1270
+ x * 2
1271
+ in Fear::None
1272
+ 'none'
1273
+ end #=> 84
1274
+
1275
+ case Fear.some(41)
1276
+ in Fear::Some(x) if x.even?
1277
+ x / 2
1278
+ in Fear::Some(x) if x.odd? && x > 0
1279
+ x * 2
1280
+ in Fear::None
1281
+ 'none'
1282
+ end #=> 82
1283
+
1284
+ case Fear.some(42)
1285
+ in Fear::Some(x) if x.odd?
1286
+ x * 2
1287
+ else
1288
+ 'nothing'
1289
+ end #=> nothing
1290
+ ```
1291
+
1292
+ It's possible to pattern match against Fear::Either and Fear::Try as well:
1293
+
1294
+ ```ruby
1295
+ case either
1296
+ in Fear::Right(Integer | String => x)
1297
+ "integer or string: #{x}"
1298
+ in Fear::Left(String => error_code) if error_code = :not_found
1299
+ 'not found'
1300
+ end
1301
+ ```
1302
+
1303
+ ```ruby
1304
+ case Fear.try { 10 / x }
1305
+ in Fear::Failure(ZeroDivisionError)
1306
+ # ..
1307
+ in Fear::Success(x)
1308
+ # ..
187
1309
  end
188
1310
  ```
189
1311
 
1312
+ ### Dry-Types integration
1313
+
1314
+ #### Option
1315
+
1316
+ NOTE: Requires the dry-tyes gem to be loaded.
1317
+
1318
+ Load the `:fear_option` extension in your application.
1319
+
1320
+ ```ruby
1321
+ require 'dry-types'
1322
+ require 'dry/types/fear'
1323
+
1324
+ Dry::Types.load_extensions(:fear_option)
1325
+
1326
+ module Types
1327
+ include Dry.Types()
1328
+ end
1329
+ ```
1330
+
1331
+ Append .option to a Type to return a `Fear::Option` object:
1332
+
1333
+ ```ruby
1334
+ Types::Option::Strict::Integer[nil]
1335
+ #=> Fear.none
1336
+ Types::Option::Coercible::String[nil]
1337
+ #=> Fear.none
1338
+ Types::Option::Strict::Integer[123]
1339
+ #=> Fear.some(123)
1340
+ Types::Option::Strict::String[123]
1341
+ #=> Fear.some(123)
1342
+ Types::Option::Coercible::Float['12.3']
1343
+ #=> Fear.some(12.3)
1344
+ ```
1345
+
1346
+ 'Option' types can also accessed by calling '.option' on a regular type:
1347
+
1348
+ ```ruby
1349
+ Types::Strict::Integer.option # equivalent to Types::Option::Strict::Integer
1350
+ ```
1351
+
1352
+
1353
+ You can define your own optional types:
1354
+
1355
+ ```ruby
1356
+ option_string = Types::Strict::String.option
1357
+ option_string[nil]
1358
+ # => Fear.none
1359
+ option_string[nil].map(&:upcase)
1360
+ # => Fear.none
1361
+ option_string['something']
1362
+ # => Fear.some('something')
1363
+ option_string['something'].map(&:upcase)
1364
+ # => Fear.some('SOMETHING')
1365
+ option_string['something'].map(&:upcase).get_or_else { 'NOTHING' }
1366
+ # => "SOMETHING"
1367
+ ```
1368
+
1369
+ You can use it with dry-struct as well:
1370
+
1371
+ ```ruby
1372
+ class User < Dry::Struct
1373
+ attribute :name, Types::Coercible::String
1374
+ attribute :age, Types::Coercible::Integer.option
1375
+ end
1376
+
1377
+ user = User.new(name: 'Bob', age: nil)
1378
+ user.name #=> "Bob"
1379
+ user.age #=> Fear.none
1380
+
1381
+ user = User.new(name: 'Bob', age: 42)
1382
+ user.age #=> Fear.some(42)
1383
+ ```
1384
+
190
1385
  ## Testing
191
1386
 
192
1387
  To simplify testing, you may use [fear-rspec](https://github.com/bolshakov/fear-rspec) gem. It
@@ -202,6 +1397,7 @@ provides a bunch of rspec matchers.
202
1397
 
203
1398
  ## Alternatives
204
1399
 
1400
+ * [algebrick](https://github.com/pitr-ch/algebrick)
205
1401
  * [deterministic](https://github.com/pzol/deterministic)
206
1402
  * [dry-monads](https://github.com/dry-rb/dry-monads)
207
1403
  * [kleisli](https://github.com/txus/kleisli)