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
data/Rakefile
CHANGED
@@ -171,6 +171,33 @@ namespace :perf do
|
|
171
171
|
require "qo"
|
172
172
|
require "dry/matcher"
|
173
173
|
|
174
|
+
task :option_match_vs_native_pattern_match do
|
175
|
+
some = Fear.some(42)
|
176
|
+
|
177
|
+
Benchmark.ips do |x|
|
178
|
+
x.report("case ... in ...") do
|
179
|
+
case some
|
180
|
+
in Fear::Some(41 => x)
|
181
|
+
x
|
182
|
+
in Fear::Some(42 => x)
|
183
|
+
x
|
184
|
+
in Fear::Some(43 => x)
|
185
|
+
x
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
x.report("Option.match") do
|
190
|
+
some.match do |m|
|
191
|
+
m.some(41, &:itself)
|
192
|
+
m.some(42, &:itself)
|
193
|
+
m.some(45, &:itself)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
x.compare!
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
174
201
|
task :qo_vs_fear_pattern_extraction do
|
175
202
|
User = Struct.new(:id, :name)
|
176
203
|
user = User.new(42, "Jane")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fear"
|
4
|
+
|
5
|
+
matcher = proc do |value|
|
6
|
+
case value
|
7
|
+
in User(admin: true, name:)
|
8
|
+
puts "Hi #{name}, you are welcome"
|
9
|
+
in User(admin: false)
|
10
|
+
puts "Only admins are allowed here"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
matcher.(User.new(1, "Jane", true))
|
15
|
+
matcher.(User.new(1, "John", false))
|
@@ -12,8 +12,11 @@ require "fear"
|
|
12
12
|
#
|
13
13
|
class BinaryTreeSet
|
14
14
|
Position = Module.new
|
15
|
+
private_constant(:Position)
|
15
16
|
Right = Module.new.include(Position)
|
17
|
+
private_constant(:Right)
|
16
18
|
Left = Module.new.include(Position)
|
19
|
+
private_constant(:Left)
|
17
20
|
|
18
21
|
def initialize(elem = 0, removed: true)
|
19
22
|
@elem = elem
|
@@ -33,6 +33,7 @@ class ToWords
|
|
33
33
|
80 => "eighty",
|
34
34
|
90 => "ninety",
|
35
35
|
}.freeze
|
36
|
+
private_constant :NUMBERS
|
36
37
|
|
37
38
|
CONVERTER = Fear.matcher do |m|
|
38
39
|
NUMBERS.each_pair do |number, in_words|
|
@@ -46,6 +47,7 @@ class ToWords
|
|
46
47
|
m.case(->(n) { n < 1_000_000 }) { |n| "#{CONVERTER.(n / 1_000)} thousands #{CONVERTER.(n % 1_000)}" }
|
47
48
|
m.else { |n| raise "#{n} too big " }
|
48
49
|
end
|
50
|
+
private_constant :CONVERTER
|
49
51
|
|
50
52
|
def self.call(number)
|
51
53
|
Fear.case(Integer, &:itself).and_then(CONVERTER).(number)
|
data/fear.gemspec
CHANGED
@@ -20,12 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^spec\/})
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.post_install_message = <<-MSG
|
24
|
-
Fear v0.11.0 introduces backwards-incompatible changes.
|
25
|
-
Please see https://github.com/bolshakov/fear/blob/master/CHANGELOG.md#0110 for details.
|
26
|
-
Successfully installed fear-#{Fear::VERSION}
|
27
|
-
MSG
|
28
|
-
|
29
23
|
spec.add_runtime_dependency "lru_redux"
|
30
24
|
spec.add_runtime_dependency "treetop"
|
31
25
|
|
@@ -35,9 +29,14 @@ Gem::Specification.new do |spec|
|
|
35
29
|
spec.add_development_dependency "dry-matcher"
|
36
30
|
spec.add_development_dependency "dry-monads"
|
37
31
|
spec.add_development_dependency "qo"
|
38
|
-
spec.add_development_dependency "rake", "~>
|
32
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
39
33
|
spec.add_development_dependency "rspec", "~> 3.1"
|
40
|
-
spec.add_development_dependency "rubocop-rspec", "1.
|
34
|
+
spec.add_development_dependency "rubocop-rspec", "1.34.0"
|
35
|
+
spec.add_development_dependency "rubocop", "1.0.0"
|
41
36
|
spec.add_development_dependency "ruby_coding_standard"
|
42
37
|
spec.add_development_dependency "yard"
|
38
|
+
spec.add_development_dependency "dry-types"
|
39
|
+
spec.add_development_dependency "fear-rspec"
|
40
|
+
spec.add_development_dependency "simplecov"
|
41
|
+
spec.add_development_dependency "simplecov-lcov"
|
43
42
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Types
|
5
|
+
class Option
|
6
|
+
include Type
|
7
|
+
include ::Dry::Equalizer(:type, :options, inspect: false, immutable: true)
|
8
|
+
include Decorator
|
9
|
+
include Builder
|
10
|
+
include Printable
|
11
|
+
|
12
|
+
# @param [Fear::Option, Object] input
|
13
|
+
#
|
14
|
+
# @return [Fear::Option]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def call_unsafe(input = Undefined)
|
18
|
+
case input
|
19
|
+
when ::Fear::Option
|
20
|
+
input
|
21
|
+
when Undefined
|
22
|
+
Fear.none
|
23
|
+
else
|
24
|
+
Fear.option(type.call_unsafe(input))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [Fear::Option, Object] input
|
29
|
+
#
|
30
|
+
# @return [Fear::Option]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def call_safe(input = Undefined)
|
34
|
+
case input
|
35
|
+
when ::Fear::Option
|
36
|
+
input
|
37
|
+
when Undefined
|
38
|
+
Fear.none
|
39
|
+
else
|
40
|
+
Fear.option(type.call_safe(input) { |output = input| return yield(output) })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [Object] input
|
45
|
+
#
|
46
|
+
# @return [Result::Success]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def try(input = Undefined)
|
50
|
+
result = type.try(input)
|
51
|
+
|
52
|
+
if result.success?
|
53
|
+
Result::Success.new(Fear.option(result.input))
|
54
|
+
else
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [true]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def default?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Object] value
|
67
|
+
#
|
68
|
+
# @see Dry::Types::Builder#default
|
69
|
+
#
|
70
|
+
# @raise [ArgumentError] if nil provided as default value
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
def default(value)
|
74
|
+
if value.nil?
|
75
|
+
raise ArgumentError, "nil cannot be used as a default of a maybe type"
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module Builder
|
83
|
+
# Turn a type into a maybe type
|
84
|
+
#
|
85
|
+
# @return [Option]
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def option
|
89
|
+
Option.new(Types["nil"] | self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
class Schema
|
95
|
+
class Key
|
96
|
+
# @api private
|
97
|
+
def option
|
98
|
+
__new__(type.option)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
class Printer
|
105
|
+
MAPPING[Option] = :visit_option
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def visit_option(maybe)
|
109
|
+
visit(maybe.type) do |type|
|
110
|
+
yield "Fear::Option<#{type}>"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Register non-coercible maybe types
|
116
|
+
NON_NIL.each_key do |name|
|
117
|
+
register("option.strict.#{name}", self[name.to_s].option)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Register coercible maybe types
|
121
|
+
COERCIBLE.each_key do |name|
|
122
|
+
register("option.coercible.#{name}", self["coercible.#{name}"].option)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/fear.rb
CHANGED
@@ -11,10 +11,19 @@ require "fear/version"
|
|
11
11
|
|
12
12
|
module Fear
|
13
13
|
Error = Class.new(StandardError)
|
14
|
+
public_constant :Error
|
15
|
+
|
14
16
|
IllegalStateException = Class.new(Error)
|
17
|
+
public_constant :IllegalStateException
|
18
|
+
|
15
19
|
MatchError = Class.new(Error)
|
20
|
+
public_constant :MatchError
|
21
|
+
|
16
22
|
NoSuchElementError = Class.new(Error)
|
23
|
+
public_constant :NoSuchElementError
|
24
|
+
|
17
25
|
PatternSyntaxError = Class.new(Error)
|
26
|
+
public_constant :PatternSyntaxError
|
18
27
|
|
19
28
|
extend EitherApi
|
20
29
|
extend ExtractorApi
|
data/lib/fear/awaitable.rb
CHANGED
@@ -9,7 +9,7 @@ module Fear
|
|
9
9
|
module Awaitable
|
10
10
|
# Await +completed+ state of this +Awaitable+
|
11
11
|
#
|
12
|
-
# @param
|
12
|
+
# @param _at_most [Fixnum] maximum timeout in seconds
|
13
13
|
# @return [Fear::Awaitable]
|
14
14
|
# @raise [Timeout::Error]
|
15
15
|
def __ready__(_at_most)
|
@@ -18,7 +18,7 @@ module Fear
|
|
18
18
|
|
19
19
|
# Await and return the result of this +Awaitable+
|
20
20
|
#
|
21
|
-
# @param
|
21
|
+
# @param _at_most [Fixnum] maximum timeout in seconds
|
22
22
|
# @return [any]
|
23
23
|
# @raise [Timeout::Error]
|
24
24
|
def __result__(_at_most)
|
data/lib/fear/either.rb
CHANGED
@@ -275,6 +275,11 @@ module Fear
|
|
275
275
|
# @return [String]
|
276
276
|
alias to_s inspect
|
277
277
|
|
278
|
+
# @return [<any>]
|
279
|
+
def deconstruct
|
280
|
+
[value]
|
281
|
+
end
|
282
|
+
|
278
283
|
class << self
|
279
284
|
# Build pattern matcher to be used later, despite off
|
280
285
|
# +Either#match+ method, id doesn't apply matcher immanently,
|
@@ -25,7 +25,10 @@ module Fear
|
|
25
25
|
# @api private
|
26
26
|
class EitherPatternMatch < Fear::PatternMatch
|
27
27
|
LEFT_EXTRACTOR = :left_value.to_proc
|
28
|
+
public_constant :LEFT_EXTRACTOR
|
29
|
+
|
28
30
|
RIGHT_EXTRACTOR = :right_value.to_proc
|
31
|
+
public_constant :RIGHT_EXTRACTOR
|
29
32
|
|
30
33
|
# Match against +Fear::Right+
|
31
34
|
#
|
data/lib/fear/extractor.rb
CHANGED
@@ -23,6 +23,7 @@ module Fear
|
|
23
23
|
autoload :ValueMatcher, "fear/extractor/value_matcher"
|
24
24
|
|
25
25
|
ExtractorNotFound = Class.new(Error)
|
26
|
+
public_constant :ExtractorNotFound
|
26
27
|
|
27
28
|
@mutex = Mutex.new
|
28
29
|
@registry = PartialFunction::EMPTY
|
@@ -30,6 +31,7 @@ module Fear
|
|
30
31
|
EXTRACTOR_NOT_FOUND = proc do |klass|
|
31
32
|
raise ExtractorNotFound, "could not find extractor for " + klass.inspect
|
32
33
|
end
|
34
|
+
private_constant :EXTRACTOR_NOT_FOUND
|
33
35
|
|
34
36
|
class << self
|
35
37
|
# @param klass [Class, String]
|
@@ -7,6 +7,7 @@ module Fear
|
|
7
7
|
# Parse pattern. Used within +Fear[]+
|
8
8
|
class Pattern
|
9
9
|
DEFAULT_PATTERN_CACHE_SIZE = 10_000
|
10
|
+
private_constant :DEFAULT_PATTERN_CACHE_SIZE
|
10
11
|
@pattern_cache = LruRedux::Cache.new(ENV.fetch("FEAR_PATTERNS_CACHE_SIZE", DEFAULT_PATTERN_CACHE_SIZE))
|
11
12
|
|
12
13
|
class << self
|
data/lib/fear/failure.rb
CHANGED
@@ -5,6 +5,7 @@ module Fear
|
|
5
5
|
include Try
|
6
6
|
include RightBiased::Left
|
7
7
|
include FailurePatternMatch.mixin
|
8
|
+
|
8
9
|
EXTRACTOR = proc do |try|
|
9
10
|
if Fear::Failure === try
|
10
11
|
Fear.some([try.exception])
|
@@ -12,6 +13,7 @@ module Fear
|
|
12
13
|
Fear.none
|
13
14
|
end
|
14
15
|
end
|
16
|
+
public_constant :EXTRACTOR
|
15
17
|
|
16
18
|
# @param [StandardError]
|
17
19
|
def initialize(exception)
|
@@ -103,5 +105,10 @@ module Fear
|
|
103
105
|
|
104
106
|
# @return [String]
|
105
107
|
alias to_s inspect
|
108
|
+
|
109
|
+
# @return [<StandardError>]
|
110
|
+
def deconstruct
|
111
|
+
[exception]
|
112
|
+
end
|
106
113
|
end
|
107
114
|
end
|
data/lib/fear/future.rb
CHANGED
@@ -298,11 +298,9 @@ module Fear
|
|
298
298
|
on_complete_match do |m|
|
299
299
|
m.case(Fear::Failure) { |failure| promise.complete!(failure) }
|
300
300
|
m.success do |value|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
promise.failure!(error)
|
305
|
-
end
|
301
|
+
yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
|
302
|
+
rescue StandardError => error
|
303
|
+
promise.failure!(error)
|
306
304
|
end
|
307
305
|
end
|
308
306
|
promise.to_future
|
@@ -380,7 +378,15 @@ module Fear
|
|
380
378
|
on_complete_match do |m|
|
381
379
|
m.success do |value|
|
382
380
|
other.on_complete do |other_try|
|
383
|
-
promise.complete!(
|
381
|
+
promise.complete!(
|
382
|
+
other_try.map do |other_value|
|
383
|
+
if block_given?
|
384
|
+
yield(value, other_value)
|
385
|
+
else
|
386
|
+
[value, other_value]
|
387
|
+
end
|
388
|
+
end,
|
389
|
+
)
|
384
390
|
end
|
385
391
|
end
|
386
392
|
m.failure do |error|
|