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
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")
@@ -9,7 +9,7 @@ matcher = Fear.matcher do |m|
9
9
  puts "Hi #{name}, you are welcome"
10
10
  end
11
11
  m.xcase("User(_, _, false)") do
12
- puts "Only admins allowed here"
12
+ puts "Only admins are allowed here"
13
13
  end
14
14
  end
15
15
 
@@ -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)
@@ -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", "~> 12.3"
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.33.0"
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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types"
4
+ require "fear"
5
+
6
+ Dry::Types.register_extension(:fear_option) do
7
+ require "dry/types/fear/option"
8
+ 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
@@ -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
@@ -9,7 +9,7 @@ module Fear
9
9
  module Awaitable
10
10
  # Await +completed+ state of this +Awaitable+
11
11
  #
12
- # @param at_most [Fixnum] maximum timeout in seconds
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 at_most [Fixnum] maximum timeout in seconds
21
+ # @param _at_most [Fixnum] maximum timeout in seconds
22
22
  # @return [any]
23
23
  # @raise [Timeout::Error]
24
24
  def __result__(_at_most)
@@ -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
  #
@@ -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]
@@ -10,7 +10,7 @@ module Fear
10
10
  end
11
11
 
12
12
  def bindings(_)
13
- EMPTY_HASH
13
+ Utils::EMPTY_HASH
14
14
  end
15
15
  end
16
16
  end
@@ -9,7 +9,7 @@ module Fear
9
9
  end
10
10
 
11
11
  def bindings(_)
12
- EMPTY_HASH
12
+ Utils::EMPTY_HASH
13
13
  end
14
14
  end
15
15
  end
@@ -13,7 +13,7 @@ module Fear
13
13
  end
14
14
 
15
15
  def bindings(_)
16
- EMPTY_HASH
16
+ Utils::EMPTY_HASH
17
17
  end
18
18
  end
19
19
  end
@@ -8,9 +8,6 @@ module Fear
8
8
  class Matcher < OpenStruct
9
9
  autoload :And, "fear/extractor/matcher/and"
10
10
 
11
- EMPTY_HASH = {}.freeze
12
- EMPTY_ARRAY = [].freeze
13
-
14
11
  # @param node [Fear::Extractor::Grammar::Node]
15
12
  def initialize(node:, **attributes)
16
13
  @input = node.input
@@ -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
@@ -12,7 +12,7 @@ module Fear
12
12
  end
13
13
 
14
14
  def bindings(_)
15
- EMPTY_HASH
15
+ Utils::EMPTY_HASH
16
16
  end
17
17
  end
18
18
  end
@@ -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
@@ -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
- begin
302
- yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
303
- rescue StandardError => error
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!(other_try.map { |other_value| [value, other_value] })
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|