fear 0.9.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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)