fear 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +39 -0
  3. data/.github/workflows/spec.yml +42 -0
  4. data/.rubocop.yml +1 -1
  5. data/.simplecov +17 -0
  6. data/CHANGELOG.md +12 -3
  7. data/Gemfile +0 -2
  8. data/Gemfile.lock +80 -39
  9. data/README.md +158 -9
  10. data/Rakefile +27 -0
  11. data/examples/pattern_extracting.rb +1 -1
  12. data/examples/pattern_extracting_ruby2.7.rb +15 -0
  13. data/examples/pattern_matching_binary_tree_set.rb +3 -0
  14. data/examples/pattern_matching_number_in_words.rb +2 -0
  15. data/fear.gemspec +7 -8
  16. data/lib/dry/types/fear.rb +8 -0
  17. data/lib/dry/types/fear/option.rb +125 -0
  18. data/lib/fear.rb +9 -0
  19. data/lib/fear/awaitable.rb +2 -2
  20. data/lib/fear/either.rb +5 -0
  21. data/lib/fear/either_pattern_match.rb +3 -0
  22. data/lib/fear/extractor.rb +2 -0
  23. data/lib/fear/extractor/any_matcher.rb +1 -1
  24. data/lib/fear/extractor/array_splat_matcher.rb +1 -1
  25. data/lib/fear/extractor/empty_list_matcher.rb +1 -1
  26. data/lib/fear/extractor/matcher.rb +0 -3
  27. data/lib/fear/extractor/pattern.rb +1 -0
  28. data/lib/fear/extractor/value_matcher.rb +1 -1
  29. data/lib/fear/failure.rb +7 -0
  30. data/lib/fear/future.rb +12 -6
  31. data/lib/fear/future_api.rb +2 -2
  32. data/lib/fear/left.rb +1 -0
  33. data/lib/fear/none.rb +18 -0
  34. data/lib/fear/option.rb +22 -0
  35. data/lib/fear/option_pattern_match.rb +1 -0
  36. data/lib/fear/partial_function.rb +6 -0
  37. data/lib/fear/partial_function/empty.rb +2 -0
  38. data/lib/fear/pattern_matching_api.rb +1 -0
  39. data/lib/fear/right.rb +2 -0
  40. data/lib/fear/some.rb +28 -0
  41. data/lib/fear/struct.rb +13 -0
  42. data/lib/fear/success.rb +6 -0
  43. data/lib/fear/try_pattern_match.rb +3 -0
  44. data/lib/fear/utils.rb +13 -0
  45. data/lib/fear/version.rb +1 -1
  46. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  47. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  48. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  49. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  50. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  51. data/spec/fear/awaitable_spec.rb +17 -0
  52. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  53. data/spec/fear/future_spec.rb +11 -2
  54. data/spec/fear/none_spec.rb +1 -1
  55. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  56. data/spec/fear/option_spec.rb +128 -0
  57. data/spec/fear/partial_function_spec.rb +50 -0
  58. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  59. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  60. data/spec/spec_helper.rb +6 -2
  61. data/spec/struct_pattern_matching_spec.rb +36 -0
  62. data/spec/struct_spec.rb +2 -2
  63. data/spec/support/dry_types.rb +6 -0
  64. metadata +110 -12
  65. data/.travis.yml +0 -17
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "support/dry_types"
4
+
5
+ RSpec.describe Dry::Types::Nominal, "#option", :option do
6
+ context "with a nominal" do
7
+ subject(:type) { Dry::Types["nominal.string"].option }
8
+
9
+ it_behaves_like "Dry::Types::Nominal without primitive"
10
+
11
+ it "returns None when value is nil" do
12
+ expect(type[nil]).to be_none
13
+ end
14
+
15
+ it "returns Some when value exists" do
16
+ expect(type["hello"]).to be_some_of("hello")
17
+ end
18
+
19
+ it "returns original if input is already a option" do
20
+ expect(type[Fear.some("hello")]).to be_some_of("hello")
21
+ end
22
+
23
+ it "aliases #[] as #call" do
24
+ expect(type.("hello")).to be_some_of("hello")
25
+ end
26
+
27
+ it "does not have primitive" do
28
+ expect(type).to_not respond_to(:primitive)
29
+ end
30
+ end
31
+
32
+ context "with a strict type" do
33
+ subject(:type) { Dry::Types["strict.integer"].option }
34
+
35
+ it_behaves_like "Dry::Types::Nominal without primitive"
36
+
37
+ it "returns None when value is nil" do
38
+ expect(type[nil]).to be_none
39
+ end
40
+
41
+ it "returns Some when value exists" do
42
+ expect(type[231]).to be_some_of(231)
43
+ end
44
+ end
45
+
46
+ context "with a sum" do
47
+ subject(:type) { Dry::Types["nominal.bool"].option }
48
+
49
+ it_behaves_like "Dry::Types::Nominal without primitive"
50
+
51
+ it "returns None when value is nil" do
52
+ expect(type[nil]).to be_none
53
+ end
54
+
55
+ it "returns Some when value exists" do
56
+ expect(type[true]).to be_some_of(true)
57
+ expect(type[false]).to be_some_of(false)
58
+ end
59
+
60
+ it "does not have primitive" do
61
+ expect(type).to_not respond_to(:primitive)
62
+ end
63
+ end
64
+
65
+ context "with keys" do
66
+ subject(:type) do
67
+ Dry::Types["hash"].schema(foo: Dry::Types["integer"]).key(:foo)
68
+ end
69
+
70
+ it "gets wrapped by key type" do
71
+ expect(type.option).to be_a(Dry::Types::Schema::Key)
72
+ expect(type.option[nil]).to be_none
73
+ expect(type.option[1]).to be_some_of(1)
74
+ end
75
+ end
76
+
77
+ describe "#try" do
78
+ subject(:type) { Dry::Types["coercible.integer"].option }
79
+
80
+ it "maps successful result" do
81
+ expect(type.try("1")).to eq(Dry::Types::Result::Success.new(Fear.some(1)))
82
+ expect(type.try(nil)).to eq(Dry::Types::Result::Success.new(Fear.none))
83
+ expect(type.try("a")).to be_a(Dry::Types::Result::Failure)
84
+ end
85
+ end
86
+
87
+ describe "#call" do
88
+ describe "safe calls" do
89
+ subject(:type) { Dry::Types["coercible.integer"].option }
90
+
91
+ specify do
92
+ expect(type.("a") { :fallback }).to be(:fallback)
93
+ expect(type.(Fear.some(1)) { :fallback }).to eq(Fear.some(1))
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::Awaitable do
4
+ subject(:awaitable) { Object.new.extend(Fear::Awaitable) }
5
+
6
+ describe "#__ready__" do
7
+ it "must implement the method" do
8
+ expect { awaitable.__ready__(1) }.to raise_error(NotImplementedError)
9
+ end
10
+ end
11
+
12
+ describe "#__result__" do
13
+ it "must implement the method" do
14
+ expect { awaitable.__result__(1) }.to raise_error(NotImplementedError)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::Either do
4
+ describe "pattern matching" do
5
+ subject do
6
+ case value
7
+ in Fear::Right[Integer => int]
8
+ "right of #{int}"
9
+ in Fear::Left[Integer => int]
10
+ "left of #{int}"
11
+ else
12
+ "something else"
13
+ end
14
+ end
15
+
16
+ context "when value is right of integer" do
17
+ let(:value) { Fear.right(42) }
18
+
19
+ it { is_expected.to eq("right of 42") }
20
+ end
21
+
22
+ context "when value is left of integer" do
23
+ let(:value) { Fear.left(42) }
24
+
25
+ it { is_expected.to eq("left of 42") }
26
+ end
27
+ end
28
+ end
@@ -398,11 +398,20 @@ RSpec.describe Fear::Future do
398
398
  subject { Fear::Await.result(future, 1) }
399
399
 
400
400
  context "successful" do
401
- let(:future) { this.zip(that) }
402
401
  let!(:this) { Fear.future { 1 } }
403
402
  let!(:that) { Fear.future { 2 } }
404
403
 
405
- it { is_expected.to eq(Fear.success([1, 2])) }
404
+ context "without a block" do
405
+ let(:future) { this.zip(that) }
406
+
407
+ it { is_expected.to eq(Fear.success([1, 2])) }
408
+ end
409
+
410
+ context "with a block" do
411
+ let(:future) { this.zip(that) { |x, y| x + y } }
412
+
413
+ it { is_expected.to eq(Fear.success(3)) }
414
+ end
406
415
  end
407
416
 
408
417
  context "first failed" do
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe Fear::None do
3
+ RSpec.describe "Fear::None" do
4
4
  it_behaves_like Fear::RightBiased::Left do
5
5
  let(:left) { Fear.none }
6
6
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::Option do
4
+ describe "pattern matching" do
5
+ subject do
6
+ case value
7
+ in Fear::Some(Integer => int)
8
+ "some of #{int}"
9
+ in Fear::None
10
+ "none"
11
+ else
12
+ "something else"
13
+ end
14
+ end
15
+
16
+ context "when value is some of integer" do
17
+ let(:value) { Fear.some(42) }
18
+
19
+ it { is_expected.to eq("some of 42") }
20
+ end
21
+
22
+ context "when value is none" do
23
+ let(:value) { Fear.none }
24
+
25
+ it { is_expected.to eq("none") }
26
+ end
27
+
28
+ context "when value is not some of integer" do
29
+ let(:value) { Fear.some("42") }
30
+
31
+ it { is_expected.to eq("something else") }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::Option do
4
+ describe "#zip" do
5
+ subject { left.zip(right) }
6
+
7
+ context "some with some" do
8
+ let(:left) { Fear.some(42) }
9
+ let(:right) { Fear.some(664) }
10
+
11
+ context "without a block" do
12
+ subject { left.zip(right) }
13
+
14
+ it { is_expected.to eq(Fear.some([42, 664])) }
15
+ end
16
+
17
+ context "with a block" do
18
+ subject { left.zip(right) { |x, y| x * y } }
19
+
20
+ it { is_expected.to eq(Fear.some(27_888)) }
21
+ end
22
+ end
23
+
24
+ context "some with none" do
25
+ let(:left) { Fear.some(42) }
26
+ let(:right) { Fear.none }
27
+
28
+ it { is_expected.to eq(Fear.none) }
29
+ end
30
+
31
+ context "none with some" do
32
+ let(:left) { Fear.none }
33
+ let(:right) { Fear.some(42) }
34
+
35
+ it { is_expected.to eq(Fear.none) }
36
+ end
37
+
38
+ context "none with none" do
39
+ let(:left) { Fear.none }
40
+ let(:right) { Fear.none }
41
+
42
+ it { is_expected.to eq(Fear.none) }
43
+ end
44
+ end
45
+
46
+ describe "#filter_map" do
47
+ subject { option.filter_map(&filter_map) }
48
+
49
+ context "some mapped to nil" do
50
+ let(:option) { Fear.some(42) }
51
+ let(:filter_map) { ->(*) { nil } }
52
+
53
+ it { is_expected.to be_none }
54
+ end
55
+
56
+ context "some mapped to false" do
57
+ let(:option) { Fear.some(42) }
58
+ let(:filter_map) { ->(*) { false } }
59
+
60
+ it { is_expected.to be_none }
61
+ end
62
+
63
+ context "some mapped to true" do
64
+ let(:option) { Fear.some(42) }
65
+ let(:filter_map) { ->(*) { true } }
66
+
67
+ it { is_expected.to be_some_of(true) }
68
+ end
69
+
70
+ context "some mapped to another value" do
71
+ let(:option) { Fear.some(42) }
72
+ let(:filter_map) { ->(x) { x / 2 if x.even? } }
73
+
74
+ it { is_expected.to be_some_of(21) }
75
+ end
76
+
77
+ context "none" do
78
+ let(:option) { Fear.none }
79
+ let(:filter_map) { ->(x) { x / 2 } }
80
+
81
+ it { is_expected.to be_none }
82
+ end
83
+ end
84
+
85
+ describe "#matcher" do
86
+ subject(:result) { matcher.(value) }
87
+
88
+ let(:matcher) do
89
+ described_class.matcher do |m|
90
+ m.some { |x| "some of #{x}" }
91
+ m.none { "none" }
92
+ end
93
+ end
94
+
95
+ context "when matches some branch" do
96
+ let(:value) { Fear.some(42) }
97
+
98
+ it { is_expected.to eq("some of 42") }
99
+ end
100
+
101
+ context "when matches none branch" do
102
+ let(:value) { Fear.none }
103
+
104
+ it { is_expected.to eq("none") }
105
+ end
106
+ end
107
+
108
+ describe "#match" do
109
+ subject(:matcher) do
110
+ described_class.match(value) do |m|
111
+ m.some { |x| "some of #{x}" }
112
+ m.none { "none" }
113
+ end
114
+ end
115
+
116
+ context "when matches some branch" do
117
+ let(:value) { Fear.some(42) }
118
+
119
+ it { is_expected.to eq("some of 42") }
120
+ end
121
+
122
+ context "when matches none branch" do
123
+ let(:value) { Fear.none }
124
+
125
+ it { is_expected.to eq("none") }
126
+ end
127
+ end
128
+ end
@@ -63,6 +63,29 @@ RSpec.describe Fear::PartialFunction do
63
63
  end
64
64
  end
65
65
 
66
+ describe ".or" do
67
+ subject { described_class.or(guard_1, guard_2, &Fear::Utils::IDENTITY) }
68
+
69
+ let(:guard_1) { ->(x) { x == 42 } }
70
+ let(:guard_2) { ->(x) { x == 21 } }
71
+
72
+ it { is_expected.to be_defined_at(42) }
73
+ it { is_expected.to be_defined_at(21) }
74
+ it { is_expected.not_to be_defined_at(20) }
75
+ end
76
+
77
+ describe ".and" do
78
+ subject { described_class.and(guard_1, guard_2, &Fear::Utils::IDENTITY) }
79
+
80
+ let(:guard_1) { ->(x) { x % 5 == 0 } }
81
+ let(:guard_2) { ->(x) { x % 2 == 0 } }
82
+
83
+ it { is_expected.to be_defined_at(10) }
84
+ it { is_expected.not_to be_defined_at(5) }
85
+ it { is_expected.not_to be_defined_at(2) }
86
+ it { is_expected.not_to be_defined_at(3) }
87
+ end
88
+
66
89
  describe "#lift" do
67
90
  let(:lifted) { partial_function.lift }
68
91
 
@@ -186,4 +209,31 @@ RSpec.describe Fear::PartialFunction do
186
209
  it { is_expected.not_to raise_error }
187
210
  end
188
211
  end
212
+
213
+ shared_examples "#or_else" do |method_name|
214
+ subject { is_even.__send__(method_name, is_odd).(value) }
215
+
216
+ let(:is_even) { Fear.case(:even?.to_proc) { |x| "#{x} is even" } }
217
+ let(:is_odd) { Fear.case(:odd?.to_proc) { |x| "#{x} is odd" } }
218
+
219
+ context "when left side is defined" do
220
+ let(:value) { 42 }
221
+
222
+ it { is_expected.to eq("42 is even") }
223
+ end
224
+
225
+ context "when left side is not defined" do
226
+ let(:value) { 21 }
227
+
228
+ it { is_expected.to eq("21 is odd") }
229
+ end
230
+ end
231
+
232
+ describe "#or_else" do
233
+ include_examples "#or_else", :or_else
234
+ end
235
+
236
+ describe "#|" do
237
+ include_examples "#or_else", :|
238
+ end
189
239
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::PatternMatchingApi do
4
+ describe "Fear.match" do
5
+ subject do
6
+ Fear.match(value) do |m|
7
+ m.case(Integer, :even?.to_proc) { |x| "#{x} is even" }
8
+ m.case(Integer, :odd?.to_proc) { |x| "#{x} is odd" }
9
+ m.else { |x| "#{x} is not a number" }
10
+ end
11
+ end
12
+
13
+ context "when one branch matches" do
14
+ let(:value) { 42 }
15
+
16
+ it { is_expected.to eq("42 is even") }
17
+ end
18
+
19
+ context "when another branch matches" do
20
+ let(:value) { 21 }
21
+
22
+ it { is_expected.to eq("21 is odd") }
23
+ end
24
+
25
+ context "when else matches" do
26
+ let(:value) { "foo" }
27
+
28
+ it { is_expected.to eq("foo is not a number") }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Fear::Try do
4
+ describe "pattern matching" do
5
+ subject do
6
+ case value
7
+ in Fear::Success[Integer => int]
8
+ "success of #{int}"
9
+ in Fear::Failure[RuntimeError]
10
+ "runtime error"
11
+ else
12
+ "something else"
13
+ end
14
+ end
15
+
16
+ context "when value is success of integer" do
17
+ let(:value) { Fear.try { 42 } }
18
+
19
+ it { is_expected.to eq("success of 42") }
20
+ end
21
+
22
+ context "when value is failure runtime error" do
23
+ let(:value) { Fear.try { raise } }
24
+
25
+ it { is_expected.to eq("runtime error") }
26
+ end
27
+
28
+ context "when value is something else" do
29
+ let(:value) { Fear.try { raise StandardError } }
30
+
31
+ it { is_expected.to eq("something else") }
32
+ end
33
+ end
34
+ end