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