Skip to content

Commit 8f57f8f

Browse files
committed
Allow operator methods to be memoized
Memoizing operators will raise a syntax error due to the operator symbols being injected into the eval'd code. We can allow operator memoization by using some mangled names instead. To (hopefully) avoid clashing with any existing method names, we've added some underscores to the names and upper-cased them.
1 parent d45d5dd commit 8f57f8f

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

lib/memoist.rb

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,34 @@
33
require 'memoist/version'
44

55
module Memoist
6+
OPERATOR_METHOD_NAMES = {
7+
'[]' => '__ELEMENT_ACCESS_OP__',
8+
'[]=' => '__ELEMENT_ASSIGNMENT_OP__',
9+
'**' => '__EXPOENTIATION_OP__',
10+
'!' => '__NEGATION_BANG_OP__',
11+
'~' => '__COMPLEMENT_OP__',
12+
'+@' => '__UNARY_PLUS_OP__',
13+
'-@' => '__UNARY_MINUS_OP__',
14+
'*' => '__MULTIPLICATION_OP__',
15+
'/' => '__DIVISION_OP__',
16+
'%' => '__MODULO_OP__',
17+
'+' => '__PLUS_OP__',
18+
'-' => '__MINUS_OP__',
19+
'>>' => '__BITWISE_SHIFT_RIGHT_OP__',
20+
'<<' => '__BITWISE_SHIFT_LEFT_OP__',
21+
'&' => '__BITWISE_AND_OP__',
22+
'^' => '__BITWISE_EXCLUSIVE_OR_OP__',
23+
'|' => '__BITWISE_OR_OP__',
24+
'<=' => '__LTE_OP__',
25+
'<' => '__LT_OP__',
26+
'>' => '__GT_OP__',
27+
'>=' => '__GTE_OP__',
28+
'<=>' => '__SPACESHIP_OP__',
29+
'==' => '__EQUALITY_OP__',
30+
'===' => '__TRIPLE_EQUALITY_OP__',
31+
'=~' => '__MATCH_OP__',
32+
}.freeze
33+
634
def self.extended(extender)
735
Memoist.memoist_eval(extender) do
836
unless singleton_class.method_defined?(:memoized_methods)
@@ -13,12 +41,16 @@ def self.memoized_methods
1341
end
1442
end
1543

44+
def self.method_name_for(method_name)
45+
OPERATOR_METHOD_NAMES.fetch(method_name.to_s, method_name)
46+
end
47+
1648
def self.memoized_ivar_for(method_name, identifier = nil)
17-
"@#{memoized_prefix(identifier)}_#{escape_punctuation(method_name)}"
49+
"@#{memoized_prefix(identifier)}_#{escape_punctuation(method_name_for(method_name))}"
1850
end
1951

2052
def self.unmemoized_method_for(method_name, identifier = nil)
21-
"#{unmemoized_prefix(identifier)}_#{method_name}".to_sym
53+
"#{unmemoized_prefix(identifier)}_#{method_name_for(method_name)}".to_sym
2254
end
2355

2456
def self.memoized_prefix(identifier = nil)

test/memoist_test.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,40 @@ def name
130130
end
131131
end
132132

133+
class OperatorMethods
134+
extend Memoist
135+
136+
OPERATORS = %w{
137+
[] []=
138+
**
139+
! ~ +@ -@
140+
* / %
141+
+ -
142+
>> <<
143+
&
144+
^ |
145+
<= < > >=
146+
<=>
147+
== === =~
148+
}.freeze
149+
150+
attr_reader :counter
151+
152+
def initialize
153+
@counter = CallCounter.new
154+
end
155+
156+
OPERATORS.each do |operator|
157+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
158+
def #{operator}(other)
159+
@counter.call(#{operator.inspect})
160+
'foo'
161+
end
162+
memoize :#{operator}
163+
EOS
164+
end
165+
end
166+
133167
module Rates
134168
extend Memoist
135169

@@ -547,4 +581,16 @@ def test_private_method_memoization
547581
assert_equal 'Yes', person.send(:is_developer?)
548582
assert_equal 1, person.is_developer_calls
549583
end
584+
585+
def test_operator_method_names
586+
operator_methods = OperatorMethods.new
587+
588+
OperatorMethods::OPERATORS.each do |operator|
589+
3.times { operator_methods.send(operator, 'bar') }
590+
591+
assert_equal 1, operator_methods.counter.count(operator)
592+
assert_equal 'foo', operator_methods.send(operator, 'bar', :reload)
593+
assert_equal 2, operator_methods.counter.count(operator)
594+
end
595+
end
550596
end

0 commit comments

Comments
 (0)