From: Robert Klemme Date: 2008-02-21T18:15:30+09:00 Subject: Re: Object#freeze as a basis for caching of method results? 2008/2/20, Shot (Piotr Szotkowski) : > Robert Klemme: > > > 2008/2/19, Shot (Piotr Szotkowski) : > > >> Is there an idiomatic way to do method-results caching in Ruby? > > > http://raa.ruby-lang.org/project/memoize/ > > Ah, perfect, thanks. From both the gem's docs and the source itself, > it looks like it nicely handles the caching, provided that either the > memoized method's result does not depend on the object state or the > object state doesn't change, right? Correct. IIRC Memoize uses the method argument array to do cache lookups. I don't know whether Memoize takes measures to avoid aliasing effects but that can be tested easily, e.g. a = [1,2,3] foo(a) a << 4 foo(a) foo must of course print something to the screen or such so you see when it's invoked. > > An alternative approach would be to use current state as cache key, > > i.e. create an immutable copy and stuff that along with calculation > > results into a Hash. > > Hm, that's actually an approach worth considering. I do need to take > memory use into account, unfortunately, but it seems this is worth > testing. > > I assume this approach highly depends on (a) making sure #freeze freezes > also all referenced objects (like in my original example) and (b) #==, > #eql? and #hash are sensibly implemented (because Hash uses them for key > comparison), right? Correct. But I believe this is true for Memoize also, i.e. if you decide to use something as key to determine whether a calculation has to be redone you better make sure it properly implements #hash, #eql? etc. > > I suggest you do not inherit Set. In that case it's easy: > > *you* define which methods change the state of your class. > > Hm, that's true; also, if I get all this right, the ones > that change the state could simply clear Memoize's cache. > > I'll see how much of the stuff inherited from Set I actually use – > quite a bit, I assume, but then I could simply make an instance variable > of @set and pass all these method calls to it (while selectively > invalidating cache)… Delegator may help here although in this case I'd probably rather explicitly forward method invocations because automatic delegation does have issues of its own, for example it does not "correct" return values: $ irb -r delegate irb(main):001:0> Delegat DelegateClass Delegater Delegator irb(main):001:0> s=[1,2,3] => [1, 2, 3] irb(main):003:0> so = SimpleDelegator.new(s) => [1, 2, 3] irb(main):004:0> so.size => 3 irb(main):005:0> so.object_id => 1073413300 irb(main):006:0> s.object_id => 1073463120 irb(main):007:0> so.each {}.object_id => 1073463120 irb(main):008:0> /so.each/ should rather return /so/ but it returns /s/. > Thanks a *ton*, Robert; as usual, your reply is both > invaluable and makes me think in the right direction. Thank you! You're welcome! I am glad that my post proved useful. Kind regards robert -- use.inject do |as, often| as.you_can - without end