qo 0.2.1 → 0.3.0

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