qo 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11b6629bbb972894e4d1ebc01916769e14a70ec4dbc0d197f990d2c6849d3ffa
4
- data.tar.gz: d609ce71af4795164999b2219f3e70892c09c0eaca4174d73d79bc3e2d988231
3
+ metadata.gz: af6c00578bf0fca7464a3823282f8de93e3f5befde82c991e1f4cee97c244c1a
4
+ data.tar.gz: 9532c9394d8874306487846fe166c11315a5e322af62d58a40ee8f5a59257cf1
5
5
  SHA512:
6
- metadata.gz: c44fe7b89ec3da320eca4007f0b207da6d2300999c23c167c494e281bbe96c7449f88419d1eec29dcc9f5bc987df3ecea9b1bb0b88afa7968c6e2f3d94b12237
7
- data.tar.gz: c4e5e607debf592180547d4f7180251e38cc09958a5a2f4961ebbe91c4a52416fb2a2ceeb26c7ba0076e24999c7333716141649aace38d4152931040d8b1dc7d
6
+ metadata.gz: 71953edde27da9876463b453c8b8909986ea8f18461fa5c9ae123a15c71fea739276dd768a85487fc51152e6e3d3e0f0c9eecbc09db95eb97fc7433584caa2b0
7
+ data.tar.gz: 754e7dce63ca7a0e9b641fe45eb2b06feeb3045804f9a3236088c0187781b50de5422eaf506f777cd92f24a674ad1ca8d5dbc2faa6eac4fdfe53cdde9b007314
data/README.md CHANGED
@@ -43,7 +43,15 @@ end
43
43
 
44
44
  # Run a select like an AR query, getting the age attribute against a range
45
45
  people.select(&Qo[age: 18..30])
46
+ ```
47
+
48
+ How about some pattern matching? There are two styles:
49
+
50
+ #### Pattern Match
51
+
52
+ The original style
46
53
 
54
+ ```
47
55
  # How about some "right-hand assignment" pattern matching
48
56
  name_longer_than_three = -> person { person.name.size > 3 }
49
57
  people_with_truncated_names = people.map(&Qo.match(
@@ -58,7 +66,25 @@ Qo.match(people.first,
58
66
  )
59
67
  ```
60
68
 
61
- Get a lot more expressiveness in your queries and transformations. Read on for the full details.
69
+ #### Pattern Match Block
70
+
71
+ The new style, likely to take over in `v1.0.0` after testing:
72
+
73
+ ```ruby
74
+ name_longer_than_three = -> person { person.name.size > 3 }
75
+ people_with_truncated_names = people.map(&Qo.match { |m|
76
+ m.when(name_longer_than_three) { |person| Person.new(person.name[0..2], person.age) }
77
+ m.else(&:itself)
78
+ })
79
+
80
+ # And standalone like a case:
81
+ Qo.match(people.first) { |m|
82
+ m.when(age: 10..19) { |person| "#{person.name} is a teen that's #{person.age} years old" }
83
+ m.else { |person| "#{person.name} is #{person.age} years old" }
84
+ }
85
+ ```
86
+
87
+ (More details coming on the difference and planned 1.0.0 APIs)
62
88
 
63
89
  ### Qo'isms
64
90
 
data/Rakefile CHANGED
@@ -44,7 +44,7 @@ def xrun_benchmark(title, **benchmarks) end
44
44
  # be readability first with performance coming later. That means that early iterations
45
45
  # may well be slower, but the net expressiveness we get is worth it in the short run.
46
46
  task :perf do
47
- puts "Running on Qo v#{Qo::VERSION} at commit #{`git rev-parse HEAD`}"
47
+ puts "Running on Qo v#{Qo::VERSION} at rev #{`git rev-parse HEAD`} - Ruby #{`ruby -v`}"
48
48
 
49
49
  # Compare simple array equality. I almost think this isn't fair to Qo considering
50
50
  # no sane dev should use it for literal 1 to 1 matches like this.
data/lib/qo.rb CHANGED
@@ -8,6 +8,7 @@ require 'qo/matchers/guard_block_matcher'
8
8
 
9
9
  # Meta Matchers
10
10
  require 'qo/matchers/pattern_match'
11
+ require 'qo/matchers/pattern_match_block'
11
12
 
12
13
  # Helpers
13
14
  require 'qo/helpers'
@@ -39,5 +39,16 @@ module Qo
39
39
  "defined with `Qo.matcher` or `Qo.m` instead of regular matchers."
40
40
  end
41
41
  end
42
+
43
+ # In the case of a Pattern Match, we should only have one "else" clause
44
+ #
45
+ # @author baweaver
46
+ # @since 0.3.0
47
+ #
48
+ class MultipleElseClauses < ArgumentError
49
+ def to_s
50
+ "Cannot have more than one `else` clause in a Pattern Match."
51
+ end
52
+ end
42
53
  end
43
54
  end
@@ -0,0 +1,115 @@
1
+ require 'qo/exceptions'
2
+
3
+ module Qo
4
+ module Matchers
5
+ # Creates a PatternMatch in the style of a block.
6
+ #
7
+ # This varies from the regular PatternMatch in that all matchers are
8
+ # provided in a more succinct block format:
9
+ #
10
+ # ```ruby
11
+ # Qo.match(target) { |m|
12
+ # m.when(/^F/, 42) { |(name, age)| "#{name} is #{age}" }
13
+ # m.else { "We need a default, right?" }
14
+ # }
15
+ # ```
16
+ #
17
+ # The Public API obscures the fact that the matcher is only called when it
18
+ # is explicitly given an argument to match against. If it is not, it will
19
+ # just return a class waiting for a target, as such:
20
+ #
21
+ # ```ruby
22
+ # def get_url(url)
23
+ # Net::HTTP.get_response(URI(url)).yield_self(&Qo.match { |m|
24
+ # m.when(Net::HTTPSuccess) { |response| response.body.size }
25
+ # m.else { |response| raise response.message }
26
+ # })
27
+ # end
28
+ #
29
+ # get_url('https://github.com/baweaver/qo')
30
+ # # => 142387
31
+ # get_url('https://github.com/baweaver/qo/does_not_exist')
32
+ # # => RuntimeError: Not Found
33
+ # ```
34
+ #
35
+ # This is intended for flexibility between singular calls and calls as a
36
+ # paramater to higher order functions like `map` and `yield_self`.
37
+ #
38
+ # This variant was inspired by ideas from Scala, Haskell, and various Ruby
39
+ # libraries dealing with Async and self-yielding blocks. Especially notable
40
+ # were websocket handlers and dry-ruby implementations.
41
+ #
42
+ # @author baweaver
43
+ # @since 0.3.0
44
+ #
45
+ class PatternMatchBlock
46
+ def initialize
47
+ @matchers = []
48
+
49
+ yield(self)
50
+ end
51
+
52
+ # Creates a match case. This is the exact same as any other `and` style
53
+ # match reflected in the public API, except that it's a Guard Block
54
+ # match being performed. That means if the left side matches, the right
55
+ # side function is invoked and that value is returned.
56
+ #
57
+ # @param *array_matchers [Array[Any]]
58
+ # Array style matchers
59
+ #
60
+ # @param **keyword_matchers [Hash[Any, Any]]
61
+ # Hash style matchers
62
+ #
63
+ # @param &fn [Proc]
64
+ # If matched, this function will be called
65
+ #
66
+ # @return [Array[GuardBlockMatcher]]
67
+ # The return of this method should not be directly depended on, but will
68
+ # provide all matchers currently present. This will likely be left for
69
+ # ease of debugging later.
70
+ def when(*array_matchers, **keyword_matchers, &fn)
71
+ @matchers << Qo::Matchers::GuardBlockMatcher.new(*array_matchers, **keyword_matchers, &fn)
72
+ end
73
+
74
+ # Else is the last statement that will be evaluated if all other parts
75
+ # fail. It should be noted that it won't magically appear, you have to
76
+ # explicitly put an `else` case in for it to catch on no match unless
77
+ # you want a `nil` return
78
+ #
79
+ # @param &fn [Proc]
80
+ # Function to call when all other matches have failed
81
+ #
82
+ # @return [Proc]
83
+ def else(&fn)
84
+ raise Qo::Exceptions::MultipleElseClauses if @else
85
+ @else = fn
86
+ end
87
+
88
+ # Proc version of a PatternMatchBlock
89
+ #
90
+ # @return [Proc]
91
+ # Any -> Any | nil
92
+ def to_proc
93
+ Proc.new { |target| self.call(target) }
94
+ end
95
+
96
+ # Immediately invokes a PatternMatch
97
+ #
98
+ # @param target [Any]
99
+ # Target to run against and pipe to the associated block if it
100
+ # "matches" any of the GuardBlocks
101
+ #
102
+ # @return [Any | nil] Result of the piped block, or nil on a miss
103
+ def call(target)
104
+ @matchers.each { |guard_block_matcher|
105
+ did_match, match_result = guard_block_matcher.call(target)
106
+ return match_result if did_match
107
+ }
108
+
109
+ return @else.call(target) if @else
110
+
111
+ nil
112
+ end
113
+ end
114
+ end
115
+ end
@@ -82,12 +82,32 @@ module Qo
82
82
  # it finds one that "matches". Once found, it will pass the target into the
83
83
  # associated matcher's block function.
84
84
  #
85
+ # @param fn [Proc]
86
+ # If provided, the pattern match will become block-style, utilizing
87
+ # PatternMatchBlock instead. If any args are provided, the first
88
+ # will be treated as the target.
89
+ #
85
90
  # @param *args [Array[Any, *GuardBlockMatcher]]
86
91
  # Collection of matchers to run, potentially prefixed by a target object
87
92
  #
88
- # @return [Qo::PatternMatch | Any]
89
- # Returns a PatternMatch waiting for a target, or an evaluated PatternMatch response
90
- def match(*args)
93
+ # @return [Qo::PatternMatchBlock]
94
+ # If a value is not provided, a block style pattern match will be returned
95
+ # that responds to proc coercion. It can be used for functions like `map`.
96
+ #
97
+ # @return [Qo::PatternMatch]
98
+ # If a value is not provided and no function is present, a PatternMatch
99
+ # will be returned, awaiting a value to match against.
100
+ #
101
+ # @return [Any]
102
+ # If a value is provided, matchers will attempt to call through on it,
103
+ # returning the result of the function.
104
+ def match(*args, &fn)
105
+ if block_given?
106
+ return args.empty? ?
107
+ Qo::Matchers::PatternMatchBlock.new(&fn) :
108
+ Qo::Matchers::PatternMatchBlock.new(&fn).call(args.first)
109
+ end
110
+
91
111
  if args.first.is_a?(Qo::Matchers::GuardBlockMatcher)
92
112
  Qo::Matchers::PatternMatch.new(*args)
93
113
  else
@@ -1,3 +1,3 @@
1
1
  module Qo
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,4 +1,5 @@
1
- Running on Qo v0.2.0 at commit ba999659acb51beec215fab4240a34298794a5bf
1
+ Running on Qo v0.3.0 at rev 77eb2544c41b33361a561178b145d33fa2409bde
2
+ - Ruby ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
2
3
 
3
4
  Array * Array - Literal
4
5
  =======================
@@ -7,15 +8,15 @@ Vanilla result: true
7
8
  Qo.and result: true
8
9
 
9
10
  Warming up --------------------------------------
10
- Vanilla 278.882k i/100ms
11
- Qo.and 83.044k i/100ms
11
+ Vanilla 275.791k i/100ms
12
+ Qo.and 77.679k i/100ms
12
13
  Calculating -------------------------------------
13
- Vanilla 8.950M (± 1.7%) i/s - 44.900M in 5.018406s
14
- Qo.and 1.062M1.2%) i/s - 5.315M in 5.003728s
14
+ Vanilla 8.805M (± 1.6%) i/s - 44.127M in 5.012793s
15
+ Qo.and 987.041k2.8%) i/s - 4.971M in 5.040741s
15
16
 
16
17
  Comparison:
17
- Vanilla: 8949563.3 i/s
18
- Qo.and: 1062331.4 i/s - 8.42x slower
18
+ Vanilla: 8805260.8 i/s
19
+ Qo.and: 987041.0 i/s - 8.92x slower
19
20
 
20
21
 
21
22
  Array * Array - Index pattern match
@@ -25,15 +26,15 @@ Vanilla result: true
25
26
  Qo.and result: true
26
27
 
27
28
  Warming up --------------------------------------
28
- Vanilla 49.757k i/100ms
29
- Qo.and 22.362k i/100ms
29
+ Vanilla 45.347k i/100ms
30
+ Qo.and 21.225k i/100ms
30
31
  Calculating -------------------------------------
31
- Vanilla 562.718k (± 4.0%) i/s - 2.836M in 5.048960s
32
- Qo.and 238.217k1.7%) i/s - 1.208M in 5.070540s
32
+ Vanilla 550.096k2.4%) i/s - 2.766M in 5.031463s
33
+ Qo.and 226.391k4.2%) i/s - 1.146M in 5.072495s
33
34
 
34
35
  Comparison:
35
- Vanilla: 562718.0 i/s
36
- Qo.and: 238216.8 i/s - 2.36x slower
36
+ Vanilla: 550096.4 i/s
37
+ Qo.and: 226391.4 i/s - 2.43x slower
37
38
 
38
39
 
39
40
  Array * Object - Predicate match
@@ -43,15 +44,15 @@ Vanilla result: false
43
44
  Qo.and result: false
44
45
 
45
46
  Warming up --------------------------------------
46
- Vanilla 145.056k i/100ms
47
- Qo.and 27.888k i/100ms
47
+ Vanilla 139.508k i/100ms
48
+ Qo.and 27.451k i/100ms
48
49
  Calculating -------------------------------------
49
- Vanilla 2.204M4.4%) i/s - 11.024M in 5.011429s
50
- Qo.and 323.232k (± 2.4%) i/s - 1.618M in 5.007142s
50
+ Vanilla 2.148M3.3%) i/s - 10.742M in 5.007009s
51
+ Qo.and 303.118k (± 2.0%) i/s - 1.537M in 5.073596s
51
52
 
52
53
  Comparison:
53
- Vanilla: 2204056.6 i/s
54
- Qo.and: 323232.5 i/s - 6.82x slower
54
+ Vanilla: 2147916.4 i/s
55
+ Qo.and: 303118.1 i/s - 7.09x slower
55
56
 
56
57
 
57
58
  Array * Array - Select index pattern match
@@ -61,15 +62,15 @@ Vanilla result: [["Robert", 22], ["Roberta", 22]]
61
62
  Qo.and result: [["Robert", 22], ["Roberta", 22]]
62
63
 
63
64
  Warming up --------------------------------------
64
- Vanilla 14.343k i/100ms
65
- Qo.and 7.454k i/100ms
65
+ Vanilla 12.651k i/100ms
66
+ Qo.and 7.024k i/100ms
66
67
  Calculating -------------------------------------
67
- Vanilla 149.518k2.9%) i/s - 760.179k in 5.088601s
68
- Qo.and 77.519k (± 2.8%) i/s - 387.608k in 5.004058s
68
+ Vanilla 141.337k8.3%) i/s - 708.456k in 5.067441s
69
+ Qo.and 73.799k (± 2.9%) i/s - 372.272k in 5.048946s
69
70
 
70
71
  Comparison:
71
- Vanilla: 149517.6 i/s
72
- Qo.and: 77519.5 i/s - 1.93x slower
72
+ Vanilla: 141336.5 i/s
73
+ Qo.and: 73799.3 i/s - 1.92x slower
73
74
 
74
75
 
75
76
  Hash * Hash - Hash intersection
@@ -79,15 +80,15 @@ Vanilla result: [{:name=>"Robert", :age=>22}, {:name=>"Roberta", :age=>22}]
79
80
  Qo.and result: [{:name=>"Robert", :age=>22}, {:name=>"Roberta", :age=>22}]
80
81
 
81
82
  Warming up --------------------------------------
82
- Vanilla 36.214k i/100ms
83
- Qo.and 5.050k i/100ms
83
+ Vanilla 34.600k i/100ms
84
+ Qo.and 4.851k i/100ms
84
85
  Calculating -------------------------------------
85
- Vanilla 411.041k (± 2.3%) i/s - 2.064M in 5.024556s
86
- Qo.and 50.029k5.0%) i/s - 252.500k in 5.060014s
86
+ Vanilla 392.298k (± 2.6%) i/s - 1.972M in 5.030828s
87
+ Qo.and 48.213k4.8%) i/s - 242.550k in 5.043068s
87
88
 
88
89
  Comparison:
89
- Vanilla: 411041.4 i/s
90
- Qo.and: 50028.6 i/s - 8.22x slower
90
+ Vanilla: 392298.2 i/s
91
+ Qo.and: 48213.1 i/s - 8.14x slower
91
92
 
92
93
 
93
94
  Hash * Object - Property match
@@ -97,13 +98,13 @@ Vanilla result: [#<struct Person name="Robert", age=22>, #<struct Person name="R
97
98
  Qo.and result: [#<struct Person name="Robert", age=22>, #<struct Person name="Roberta", age=22>]
98
99
 
99
100
  Warming up --------------------------------------
100
- Vanilla 36.167k i/100ms
101
- Qo.and 5.263k i/100ms
101
+ Vanilla 32.783k i/100ms
102
+ Qo.and 4.422k i/100ms
102
103
  Calculating -------------------------------------
103
- Vanilla 411.057k3.4%) i/s - 2.062M in 5.021612s
104
- Qo.and 52.776k 4.0%) i/s - 268.413k in 5.094233s
104
+ Vanilla 361.140k9.7%) i/s - 1.803M in 5.053763s
105
+ Qo.and 46.667k10.8%) i/s - 229.944k in 5.002258s
105
106
 
106
107
  Comparison:
107
- Vanilla: 411057.2 i/s
108
- Qo.and: 52775.9 i/s - 7.79x slower
108
+ Vanilla: 361140.4 i/s
109
+ Qo.and: 46667.1 i/s - 7.74x slower
109
110
 
data/qo.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["keystonelemur@gmail.com"]
11
11
 
12
12
  spec.summary = %q{Qo is a querying library for Ruby pattern matching}
13
- spec.homepage = "https://www.github.com/baweaver/q"
13
+ spec.homepage = "https://www.github.com/baweaver/qo"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Weaver
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-18 00:00:00.000000000 Z
11
+ date: 2018-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -182,11 +182,12 @@ files:
182
182
  - lib/qo/matchers/guard_block_matcher.rb
183
183
  - lib/qo/matchers/hash_matcher.rb
184
184
  - lib/qo/matchers/pattern_match.rb
185
+ - lib/qo/matchers/pattern_match_block.rb
185
186
  - lib/qo/public_api.rb
186
187
  - lib/qo/version.rb
187
188
  - performance_report.txt
188
189
  - qo.gemspec
189
- homepage: https://www.github.com/baweaver/q
190
+ homepage: https://www.github.com/baweaver/qo
190
191
  licenses:
191
192
  - MIT
192
193
  metadata: {}