How should I avoid memoization causing bugs in Ruby?

大憨熊 提交于 2019-12-12 08:34:53

问题


Is there a consensus on how to avoid memoization causing bugs due to mutable state?

In this example, a cached result had its state mutated, and therefore gave the wrong result the second time it was called.

class Greeter

  def initialize
    @greeting_cache = {}
  end

  def expensive_greeting_calculation(formality)
    case formality
      when :casual then "Hi"
      when :formal then "Hello"
    end
  end

  def greeting(formality)
    unless @greeting_cache.has_key?(formality)
      @greeting_cache[formality] = expensive_greeting_calculation(formality)
    end
    @greeting_cache[formality]
  end

end

def memoization_mutator
  greeter = Greeter.new
  first_person = "Bob"
  # Mildly contrived in this case,
  # but you could encounter this in more complex scenarios
  puts(greeter.greeting(:casual) << " " << first_person) # => Hi Bob
  second_person = "Sue"
  puts(greeter.greeting(:casual) << " " << second_person) # => Hi Bob Sue
end

memoization_mutator

Approaches I can see to avoid this are:

  1. greeting could return a dup or clone of @greeting_cache[formality]
  2. greeting could freeze the result of @greeting_cache[formality]. That'd cause an exception to be raised when memoization_mutator appends strings to it.
  3. Check all code that uses the result of greeting to ensure none of it does any mutating of the string.

Is there a consensus on the best approach? Is the only disadvantage of doing (1) or (2) decreased performance? (I also suspect freezing an object may not work fully if it has references to other objects)

Side note: this problem doesn't affect the main application of memoization: as Fixnums are immutable, calculating Fibonacci sequences doesn't have problems with mutable state. :)


回答1:


I would lean towards returning a cloned object. The performance hit of creating a new string is next to nothing. And freezing exposes implementation details.




回答2:


I am still 'ruby newbie', and I don't know if you were aware about the difference between '<<' and '+' methods to a String.

first_person = "Bob"
puts(greeter.greeting(:casual) + " " + first_person) # => Hi Bob
second_person = "Sue"
puts(greeter.greeting(:casual) + " " + second_person) # => Hi Sue

# str << obj → str
# str + other_str → new_str


来源:https://stackoverflow.com/questions/4675967/how-should-i-avoid-memoization-causing-bugs-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!