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.
- checksums.yaml +5 -5
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +4 -12
- data/.simplecov +17 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +5 -2
- data/Gemfile.lock +130 -0
- data/LICENSE.txt +1 -1
- data/README.md +1293 -97
- data/Rakefile +369 -1
- data/benchmarks/README.md +1 -0
- data/benchmarks/dry_do_vs_fear_for.txt +11 -0
- data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
- data/benchmarks/factorial.txt +16 -0
- data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
- data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
- data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
- data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
- data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
- data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
- data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
- data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
- data/examples/pattern_extracting.rb +17 -0
- data/examples/pattern_extracting_ruby2.7.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +101 -0
- data/examples/pattern_matching_number_in_words.rb +60 -0
- data/fear.gemspec +34 -23
- data/lib/dry/types/fear.rb +8 -0
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/fear.rb +65 -15
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +131 -71
- data/lib/fear/either_api.rb +23 -0
- data/lib/fear/either_pattern_match.rb +53 -0
- data/lib/fear/empty_partial_function.rb +38 -0
- data/lib/fear/extractor.rb +112 -0
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
- data/lib/fear/extractor/any_matcher.rb +17 -0
- data/lib/fear/extractor/array_head_matcher.rb +36 -0
- data/lib/fear/extractor/array_matcher.rb +40 -0
- data/lib/fear/extractor/array_splat_matcher.rb +16 -0
- data/lib/fear/extractor/empty_list_matcher.rb +20 -0
- data/lib/fear/extractor/extractor_matcher.rb +44 -0
- data/lib/fear/extractor/grammar.rb +203 -0
- data/lib/fear/extractor/grammar.treetop +129 -0
- data/lib/fear/extractor/identifier_matcher.rb +18 -0
- data/lib/fear/extractor/matcher.rb +53 -0
- data/lib/fear/extractor/matcher/and.rb +38 -0
- data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
- data/lib/fear/extractor/pattern.rb +58 -0
- data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
- data/lib/fear/extractor/value_matcher.rb +19 -0
- data/lib/fear/extractor_api.rb +35 -0
- data/lib/fear/failure.rb +46 -14
- data/lib/fear/failure_pattern_match.rb +10 -0
- data/lib/fear/for.rb +37 -95
- data/lib/fear/for_api.rb +68 -0
- data/lib/fear/future.rb +497 -0
- data/lib/fear/future_api.rb +21 -0
- data/lib/fear/left.rb +19 -2
- data/lib/fear/left_pattern_match.rb +11 -0
- data/lib/fear/none.rb +67 -3
- data/lib/fear/none_pattern_match.rb +14 -0
- data/lib/fear/option.rb +120 -56
- data/lib/fear/option_api.rb +40 -0
- data/lib/fear/option_pattern_match.rb +48 -0
- data/lib/fear/partial_function.rb +176 -0
- data/lib/fear/partial_function/and_then.rb +50 -0
- data/lib/fear/partial_function/any.rb +28 -0
- data/lib/fear/partial_function/combined.rb +53 -0
- data/lib/fear/partial_function/empty.rb +10 -0
- data/lib/fear/partial_function/guard.rb +80 -0
- data/lib/fear/partial_function/guard/and.rb +38 -0
- data/lib/fear/partial_function/guard/and3.rb +41 -0
- data/lib/fear/partial_function/guard/or.rb +38 -0
- data/lib/fear/partial_function/lifted.rb +23 -0
- data/lib/fear/partial_function/or_else.rb +64 -0
- data/lib/fear/partial_function_class.rb +38 -0
- data/lib/fear/pattern_match.rb +114 -0
- data/lib/fear/pattern_matching_api.rb +137 -0
- data/lib/fear/promise.rb +95 -0
- data/lib/fear/right.rb +20 -2
- data/lib/fear/right_biased.rb +6 -14
- data/lib/fear/right_pattern_match.rb +11 -0
- data/lib/fear/some.rb +55 -3
- data/lib/fear/some_pattern_match.rb +13 -0
- data/lib/fear/struct.rb +248 -0
- data/lib/fear/success.rb +35 -5
- data/lib/fear/success_pattern_match.rb +12 -0
- data/lib/fear/try.rb +136 -79
- data/lib/fear/try_api.rb +33 -0
- data/lib/fear/try_pattern_match.rb +33 -0
- data/lib/fear/unit.rb +32 -0
- data/lib/fear/utils.rb +39 -14
- data/lib/fear/version.rb +4 -1
- data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
- data/spec/dry/types/fear/option/core_spec.rb +77 -0
- data/spec/dry/types/fear/option/default_spec.rb +21 -0
- data/spec/dry/types/fear/option/hash_spec.rb +58 -0
- data/spec/dry/types/fear/option/option_spec.rb +97 -0
- data/spec/fear/awaitable_spec.rb +17 -0
- data/spec/fear/done_spec.rb +8 -6
- data/spec/fear/either/mixin_spec.rb +17 -0
- data/spec/fear/either_pattern_match_spec.rb +37 -0
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/extractor/array_matcher_spec.rb +230 -0
- data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
- data/spec/fear/extractor/grammar_array_spec.rb +25 -0
- data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
- data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
- data/spec/fear/extractor/pattern_spec.rb +34 -0
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
- data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
- data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
- data/spec/fear/extractor_api_spec.rb +115 -0
- data/spec/fear/extractor_spec.rb +61 -0
- data/spec/fear/failure_spec.rb +145 -45
- data/spec/fear/for_spec.rb +57 -67
- data/spec/fear/future_spec.rb +691 -0
- data/spec/fear/guard_spec.rb +103 -0
- data/spec/fear/left_spec.rb +112 -46
- data/spec/fear/none_spec.rb +114 -16
- data/spec/fear/option/mixin_spec.rb +39 -0
- data/spec/fear/option_pattern_match_spec.rb +35 -0
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +121 -8
- data/spec/fear/partial_function/empty_spec.rb +38 -0
- data/spec/fear/partial_function_and_then_spec.rb +147 -0
- data/spec/fear/partial_function_composition_spec.rb +82 -0
- data/spec/fear/partial_function_or_else_spec.rb +276 -0
- data/spec/fear/partial_function_spec.rb +239 -0
- data/spec/fear/pattern_match_spec.rb +93 -0
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/promise_spec.rb +96 -0
- data/spec/fear/right_biased/left.rb +29 -32
- data/spec/fear/right_biased/right.rb +51 -54
- data/spec/fear/right_spec.rb +109 -41
- data/spec/fear/some_spec.rb +80 -15
- data/spec/fear/success_spec.rb +99 -32
- data/spec/fear/try/mixin_spec.rb +19 -0
- data/spec/fear/try_pattern_match_spec.rb +37 -0
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/fear/utils_spec.rb +16 -14
- data/spec/spec_helper.rb +13 -7
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +226 -0
- data/spec/support/dry_types.rb +6 -0
- metadata +320 -29
- data/.travis.yml +0 -9
- data/lib/fear/done.rb +0 -22
- data/lib/fear/for/evaluation_context.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27594ab99bc8a361057d1222bd4e70aa49f5c8535c95f622f338ab17e216cd31
|
4
|
+
data.tar.gz: dfbd5982e2da247cdb74edb7a218023195c4806d56af786098e294165bf345ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/.rubocop.yml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
inherit_gem:
|
2
|
-
|
2
|
+
ruby_coding_standard: .rubocop.yml
|
3
3
|
|
4
|
-
|
5
|
-
|
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
|
-
-
|
7
|
+
- vendor/**/*
|
data/.simplecov
ADDED
@@ -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
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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
|
-
|
8
|
+
gem "irb"
|
9
|
+
gem "qo", github: "baweaver/qo"
|
data/Gemfile.lock
ADDED
@@ -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
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Fear
|
2
|
-
[](https://travis-ci.org/bolshakov/fear)
|
3
|
-
[](https://codeclimate.com/github/bolshakov/functional/maintainability)
|
4
|
-
[](https://codeclimate.com/github/bolshakov/functional/test_coverage)
|
5
2
|
[](https://badge.fury.io/rb/fear)
|
3
|
+

|
4
|
+
[](https://codeclimate.com/github/bolshakov/fear/maintainability)
|
5
|
+
[](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
|
-
|
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
|
-
|
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
|
-
|
725
|
+
f = Fear.future { ... } # call external service
|
726
|
+
f.on_success do |result|
|
727
|
+
# handle service response
|
728
|
+
end
|
38
729
|
|
39
|
-
|
40
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
52
|
-
having to check for the existence of a value.
|
746
|
+
In sake of convenience `#on_success` callback aliased as `#each`.
|
53
747
|
|
54
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
761
|
+
Fear.future { open('http://example.com').read }
|
762
|
+
.transform(
|
763
|
+
->(value) { ... },
|
764
|
+
->(error) { ... },
|
765
|
+
)
|
66
766
|
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
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
|
-
|
797
|
+
future = Fear.future { fail 'error' }
|
798
|
+
fallback = Fear.future { 5 }
|
799
|
+
future.fallback_to(fallback) # evaluates to 5
|
800
|
+
```
|
96
801
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
878
|
+
it is translated to:
|
134
879
|
|
135
880
|
```ruby
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
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
|
-
|
148
|
-
divisor = Try { Integer('ehuton') }
|
912
|
+
#### Syntax
|
149
913
|
|
150
|
-
|
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
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
952
|
+
|
953
|
+
It's also possible to create matcher and use it several times:
|
162
954
|
|
163
955
|
```ruby
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
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
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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)
|