Skip to content

Commit 273c9f4

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 3ce2b0d commit 273c9f4

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
@@ -136,6 +136,40 @@ def name
136136
end
137137
end
138138

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

@@ -560,4 +594,16 @@ def test_private_method_memoization
560594
assert_equal 'Yes', person.send(:is_developer?)
561595
assert_equal 1, person.is_developer_calls
562596
end
597+
598+
def test_operator_method_names
599+
operator_methods = OperatorMethods.new
600+
601+
OperatorMethods::OPERATORS.each do |operator|
602+
3.times { operator_methods.send(operator, 'bar') }
603+
604+
assert_equal 1, operator_methods.counter.count(operator)
605+
assert_equal 'foo', operator_methods.send(operator, 'bar', :reload)
606+
assert_equal 2, operator_methods.counter.count(operator)
607+
end
608+
end
563609
end

0 commit comments

Comments
 (0)