fear 1.1.0 → 1.2.0

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