135 lines
3.4 KiB
Plaintext
135 lines
3.4 KiB
Plaintext
= Memoizable
|
|
|
|
Memoizable allows
|
|
|
|
require 'facets/memoizable'
|
|
|
|
The classic example is calculating the Fibonacci sequence.
|
|
|
|
class Fibonacci
|
|
include Memoizable
|
|
|
|
def fib( num )
|
|
return num if num < 2
|
|
fib(num - 1) + fib(num - 2)
|
|
end
|
|
|
|
memoize :fib
|
|
end
|
|
|
|
The first time will take a moment to calculate.
|
|
|
|
f = Fibonacci.new
|
|
f.fib(100)
|
|
|
|
The second time will be returned almost instantly.
|
|
|
|
f = Fibonacci.new
|
|
f.fib(100)
|
|
|
|
The following example makes if very clear what memoize is doing.
|
|
|
|
class MemoExample
|
|
include Memoizable
|
|
attr_accessor :a
|
|
memoize :a
|
|
end
|
|
|
|
m = MemoExample.new
|
|
m.a = 10
|
|
m.a #=> 10
|
|
m.a = 20
|
|
m.a #=> 10
|
|
|
|
Even though @a is assigned to 20, the method #a continues to return 10.
|
|
|
|
Memoizable is designed to handle a number of typically problematic aspects
|
|
of memoization implementations. First a memoizable object can be frozen.
|
|
|
|
f = Fibonacci.new
|
|
f.freeze
|
|
f.fib(100)
|
|
|
|
Memoizable objects are freezable because it does not use an instance variable
|
|
to store the memoized return values. Instead it uses a global class instance
|
|
variable in +Memoizable.cache+.
|
|
|
|
However, this also means the memoization cache is not preserved when an object
|
|
is duplicated. If this behavior is desired an aditional module is provided
|
|
called `Memoizable::Copy`. When the `Memoiazble::Copy` module is included into
|
|
a class it not only adds the #memoize class methods but also includes
|
|
an #initialize_copy method, which copies the appropriate cache entries when
|
|
#dup or #clone is used. Keep this in mind. It is important to call #super if you
|
|
override the #initialize_copy method in a class that uses `Memoizable::Copy`.
|
|
|
|
class MemoExample2
|
|
include Memoizable::Copy
|
|
attr_accessor :a
|
|
memoize :a
|
|
end
|
|
|
|
m = MemoExample2.new
|
|
m.a = 10
|
|
m.a #=> 10
|
|
|
|
n = m.dup
|
|
n.a = 20
|
|
n.a #=> 10
|
|
|
|
The `#memoize` method also accepts a few options that can be used to modify
|
|
the behvior of the memoizaiton. To memoize a method across all objects
|
|
of the same class and not just per-object, set the `:class` option to true.
|
|
|
|
class MemoExample3
|
|
include Memoizable
|
|
attr_accessor :a
|
|
memoize :a, :class=>true
|
|
end
|
|
|
|
m = MemoExample3.new
|
|
m.a = 10
|
|
m.a #=> 10
|
|
|
|
n = MemoExample3.new
|
|
n.a #=> 10
|
|
|
|
Memoization is indexed based on arguments passed to the method, as can be seen
|
|
in the Fibonacci example. Methods can also be memoized by name only, forgoing
|
|
any indexing based on arguments passed. To do this, set the `:arguments`
|
|
option to +false+.
|
|
|
|
class MemoExample4
|
|
include Memoizable
|
|
def f(n)
|
|
n + n
|
|
end
|
|
memoize :f, :arguments=>false
|
|
end
|
|
|
|
m = MemoExample4.new
|
|
m.f(1)
|
|
m.f(2) #=> 2
|
|
m.f(3) #=> 2
|
|
|
|
Blocks passed to a method normally have only a present vs. non-present effect
|
|
on the memoization index. In other words the return value of a method will be
|
|
cached under one key if a block is passed to it and under a different index
|
|
if not. It doesn't matter if blocks passed in actually differ, since
|
|
the only way to compare blocks is by their +object_id+ and it is very rare
|
|
for the exact same block object to be reused.
|
|
|
|
Finally, the `:freeze` option can be used to freeze the memoized results.
|
|
|
|
class MemoExample5
|
|
include Memoizable
|
|
def f(n)
|
|
(n + n).to_s
|
|
end
|
|
memoize :f, :freeze=>true
|
|
end
|
|
|
|
m = MemoExample5.new
|
|
m.f(1).frozen? #=> true
|
|
m.f(2).frozen? #=> true
|
|
|