abstractivator 0.13.0 → 0.14.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
|
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
|