abstractivator 0.13.0 → 0.14.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
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa5f0441439d94fb15db90acbcc41c99f378d5fd
|
4
|
+
data.tar.gz: 66885acd1df0f4322a21dcbedf41da2028416604
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db0062035f743c6a51c716ff3b52db47ed96f16b96333d858f0e698d1d864153cabcff4f5bad10ab4f6ad75101ef0742a4199e57f2cfef37328e116b16596d8f
|
7
|
+
data.tar.gz: e120d0ba2180cb4dda23e6d9a9a57043b277a2cab2fd8c720a7c96b6c8e73f86ac1e4ec834b2f4db5477339c5309ff479d36c2599f14e50dfce939a5d3daf5d7
|
@@ -9,8 +9,8 @@ module Enumerable
|
|
9
9
|
# returns an array of 2-element arrays, each of which is a left/right pair.
|
10
10
|
def self.outer_join(left, right, get_left_key, get_right_key, left_default, right_default)
|
11
11
|
|
12
|
-
ls = left.
|
13
|
-
rs = right.
|
12
|
+
ls = left.inject({}) { |h, x| h.store(get_left_key.call(x), x); h } # Avoid #hash_map and Array#to_h
|
13
|
+
rs = right.inject({}) { |h, x| h.store(get_right_key.call(x), x); h } # for better performance
|
14
14
|
|
15
15
|
raise 'duplicate left keys' if ls.size < left.size
|
16
16
|
raise 'duplicate right keys' if rs.size < right.size
|
@@ -68,29 +68,56 @@ class Proc
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
LooseCallInfo = Struct.new(:params, :accepts_arg_splat, :total_arity, :req_arity,
|
72
|
+
:requires_kw_customization, :all_key_names, :kw_padding)
|
73
|
+
|
74
|
+
# Tries to coerce x into a procedure, then calls it with the given argument list.
|
73
75
|
# If x cannot be coerced into a procedure, returns x.
|
76
|
+
# This method is optimized for use cases typically found in tight loops,
|
77
|
+
# namely where x is either a symbol or a keyword-less fixed-arity proc.
|
78
|
+
# It attempts to minimize the number of intermediate arrays created for these cases
|
79
|
+
# (as would be produced by calls to #map, #select, #take, #pad_right, etc.)
|
80
|
+
# CPU overhead created by loose_call is bad, but unexpected memory consumption would
|
81
|
+
# be worse, considering Proc#call has zero memory footprint.
|
82
|
+
# These optimizations produce a ~5x speedup, which is still 2-4x slower than
|
83
|
+
# regular Proc#call.
|
74
84
|
def self.loose_call(x, args, kws={}, &block)
|
85
|
+
return x.to_proc.call(*args) if x.is_a?(Symbol) # optimization for a typical use case
|
75
86
|
x = x.to_proc if x.respond_to?(:to_proc)
|
76
|
-
x.callable?
|
77
|
-
|
87
|
+
return x unless x.callable?
|
88
|
+
|
89
|
+
# cache proc info for performance
|
90
|
+
info = x.instance_variable_get(:@loose_call_info)
|
91
|
+
unless info
|
92
|
+
params = x.parameters
|
93
|
+
info = LooseCallInfo.new
|
94
|
+
info.params = params
|
95
|
+
info.req_arity = params.count { |p| p.first == :req }
|
96
|
+
info.total_arity = info.req_arity + params.count { |p| p.first == :opt }
|
97
|
+
info.accepts_arg_splat = params.any? { |p| p.first == :rest }
|
98
|
+
accepts_kw_splat = params.any? { |p| p.first == :keyrest }
|
99
|
+
has_kw_args = params.any? { |(type, name)| (type == :key || type == :keyreq) && !name.nil? }
|
100
|
+
info.requires_kw_customization = has_kw_args && !accepts_kw_splat
|
101
|
+
if info.requires_kw_customization
|
102
|
+
opt_key_names = info.params.select { |(type, name)| type == :key && !name.nil? }.map(&:value)
|
103
|
+
req_key_names = info.params.select { |(type, name)| type == :keyreq && !name.nil? }.map(&:value)
|
104
|
+
info.all_key_names = opt_key_names + req_key_names
|
105
|
+
info.kw_padding = req_key_names.hash_map { nil }
|
106
|
+
end
|
107
|
+
x.instance_variable_set(:@loose_call_info, info)
|
108
|
+
end
|
109
|
+
|
78
110
|
# customize args
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
unless accepts_arg_splat
|
83
|
-
args = args.take(total_arity).pad_right(req_arity)
|
111
|
+
unless info.accepts_arg_splat
|
112
|
+
args = args.take(info.total_arity) if args.size > info.total_arity
|
113
|
+
args = args.pad_right(info.req_arity) if args.size < info.req_arity
|
84
114
|
end
|
115
|
+
|
85
116
|
# customize keywords
|
86
|
-
|
87
|
-
|
88
|
-
opt_key_names = x.parameters.select{|(type, name)| type == :key && !name.nil?}.map(&:value)
|
89
|
-
req_key_names = x.parameters.select{|(type, name)| type == :keyreq && !name.nil?}.map(&:value)
|
90
|
-
all_key_names = opt_key_names + req_key_names
|
91
|
-
padding = req_key_names.hash_map{nil}
|
92
|
-
kws = padding.merge(kws.select{|k| all_key_names.include?(k)})
|
117
|
+
if info.requires_kw_customization
|
118
|
+
kws = info.kw_padding.merge(kws.select { |k| info.all_key_names.include?(k) })
|
93
119
|
end
|
120
|
+
|
94
121
|
if kws.any?
|
95
122
|
x.call(*args, **kws, &block)
|
96
123
|
else
|
@@ -62,7 +62,6 @@ context 'in the world of functional programming' do
|
|
62
62
|
expect(events).to eql [0, 1, 2, 3] # lambdas are strict in arity,
|
63
63
|
# so if their bodies executed, then we know
|
64
64
|
# they were called with the correct number of arguments
|
65
|
-
|
66
65
|
end
|
67
66
|
it 'pads with nils' do
|
68
67
|
verify_called_with ->(a, b) {[a, b]},
|
@@ -120,11 +119,18 @@ context 'in the world of functional programming' do
|
|
120
119
|
end
|
121
120
|
|
122
121
|
def verify_called_with(p, input_args, output)
|
123
|
-
|
122
|
+
2.times do
|
123
|
+
# Run twice to ensure the LooseCallInfo caching works.
|
124
|
+
# This is not paranoia; there actually was a bug thanks
|
125
|
+
# to ruby's sloppy scope.
|
126
|
+
expect(do_call(p, input_args, {})).to eql output
|
127
|
+
end
|
124
128
|
end
|
125
129
|
|
126
130
|
def verify_called_with_keywords(p, input_kws, output)
|
127
|
-
|
131
|
+
2.times do
|
132
|
+
expect(do_call(p, [], input_kws)).to eql output
|
133
|
+
end
|
128
134
|
end
|
129
135
|
end
|
130
136
|
|
@@ -138,6 +144,33 @@ context 'in the world of functional programming' do
|
|
138
144
|
expect(Proc.loose_call(:to_s, ['5'])).to eql '5'
|
139
145
|
end
|
140
146
|
|
147
|
+
# # Uncomment this to test performance. It runs several typical
|
148
|
+
# # scenarios through both Proc#call and Proc#loose_call to
|
149
|
+
# # evaluate relative performance.
|
150
|
+
# # As of this commit, loose_call was 2-4x slower.
|
151
|
+
# it 'has reasonable performance' do
|
152
|
+
# require 'benchmark'
|
153
|
+
# Benchmark.bm do |bm|
|
154
|
+
# bench = proc do |label, proc_ish, args, kws|
|
155
|
+
# bm.report("call #{label}") do
|
156
|
+
# proc_ish = proc_ish.to_proc if proc_ish.is_a?(Symbol)
|
157
|
+
# if kws.none?
|
158
|
+
# 1_000_000.times { proc_ish.call(*args) }
|
159
|
+
# else
|
160
|
+
# 1_000_000.times { proc_ish.call(*args, **kws) }
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
# bm.report("loose_call #{label}") do
|
164
|
+
# 1_000_000.times { Proc.loose_call(proc_ish, args, kws) }
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
# bench.('symbol ', :to_s, [42], {})
|
168
|
+
# bench.('proc ', proc { |x| x.to_s }, [42], {})
|
169
|
+
# bench.('lambda ', lambda { |x| x.to_s }, [42], {})
|
170
|
+
# bench.('keyword', proc { |a:| a.to_s }, [], {a: 42})
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
|
141
174
|
def do_call(p, args, kws={}, &block)
|
142
175
|
Proc.loose_call(p, args, kws, &block)
|
143
176
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abstractivator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Winton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|