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.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.rubocop.yml +1 -1
- data/.simplecov +17 -0
- data/CHANGELOG.md +12 -3
- data/Gemfile +0 -2
- data/Gemfile.lock +80 -39
- data/README.md +158 -9
- data/Rakefile +27 -0
- data/examples/pattern_extracting.rb +1 -1
- data/examples/pattern_extracting_ruby2.7.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +3 -0
- data/examples/pattern_matching_number_in_words.rb +2 -0
- data/fear.gemspec +7 -8
- data/lib/dry/types/fear.rb +8 -0
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/fear.rb +9 -0
- data/lib/fear/awaitable.rb +2 -2
- data/lib/fear/either.rb +5 -0
- data/lib/fear/either_pattern_match.rb +3 -0
- data/lib/fear/extractor.rb +2 -0
- data/lib/fear/extractor/any_matcher.rb +1 -1
- data/lib/fear/extractor/array_splat_matcher.rb +1 -1
- data/lib/fear/extractor/empty_list_matcher.rb +1 -1
- data/lib/fear/extractor/matcher.rb +0 -3
- data/lib/fear/extractor/pattern.rb +1 -0
- data/lib/fear/extractor/value_matcher.rb +1 -1
- data/lib/fear/failure.rb +7 -0
- data/lib/fear/future.rb +12 -6
- data/lib/fear/future_api.rb +2 -2
- data/lib/fear/left.rb +1 -0
- data/lib/fear/none.rb +18 -0
- data/lib/fear/option.rb +22 -0
- data/lib/fear/option_pattern_match.rb +1 -0
- data/lib/fear/partial_function.rb +6 -0
- data/lib/fear/partial_function/empty.rb +2 -0
- data/lib/fear/pattern_matching_api.rb +1 -0
- data/lib/fear/right.rb +2 -0
- data/lib/fear/some.rb +28 -0
- data/lib/fear/struct.rb +13 -0
- data/lib/fear/success.rb +6 -0
- data/lib/fear/try_pattern_match.rb +3 -0
- data/lib/fear/utils.rb +13 -0
- data/lib/fear/version.rb +1 -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/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/future_spec.rb +11 -2
- data/spec/fear/none_spec.rb +1 -1
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +128 -0
- data/spec/fear/partial_function_spec.rb +50 -0
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/spec_helper.rb +6 -2
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +2 -2
- data/spec/support/dry_types.rb +6 -0
- metadata +110 -12
- 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
|
data/spec/fear/future_spec.rb
CHANGED
@@ -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
|
-
|
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
|
data/spec/fear/none_spec.rb
CHANGED
@@ -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
|