Module: MemoWise

Defined in:
lib/memo_wise.rb,
lib/memo_wise/version.rb

Overview

MemoWise is the wise choice for memoization in Ruby.

  • Q: What is memoization?
  • A: via Wikipedia:

     [Memoization is] an optimization technique used primarily to speed up
     computer programs by storing the results of expensive function
     calls and returning the cached result when the same inputs occur
     again.
    

To start using MemoWise in a class or module:

  1. Add prepend MemoWise to the top of the class or module
  2. Call MemoWise.memo_wise to implement memoization for a given method

See Also:

Constant Summary collapse

VERSION =
"0.1.0"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.memo_wise(method_name) ⇒ void

This method returns an undefined value.

Implements memoization for the given method name.

  • Q: What does it mean to "implement memoization"?
  • A: To wrap the original method such that, for any given set of arguments, the original method will be called at most once. The result of that call will be stored on the object. All future calls to the same method with the same set of arguments will then return that saved result.

Methods which implicitly or explicitly take block arguments cannot be memoized.

Examples:

class Example
  prepend MemoWise

  def method_to_memoize(x)
    @method_called_times = (@method_called_times || 0) + 1
  end
  memo_wise :method_to_memoize
end

ex = Example.new

ex.method_to_memoize("a") #=> 1
ex.method_to_memoize("a") #=> 1

ex.method_to_memoize("b") #=> 2
ex.method_to_memoize("b") #=> 2

Parameters:

  • method_name (Symbol)

    Name of method for which to implement memoization.


# File 'lib/memo_wise.rb', line 237

Instance Method Details

#initializeObject

Constructor to setup memoization state before calling the original constructor.

This approach supports memoization on frozen (immutable) objects -- for example, classes created by the Values gem.


42
43
44
45
# File 'lib/memo_wise.rb', line 42

def initialize(*)
  @_memo_wise = Hash.new { |h, k| h[k] = {} }
  super
end

#preset_memo_wise(method_name, *args, **kwargs) ⇒ void

This method returns an undefined value.

Presets the memoized result for the given method to the result of the given block.

This method is for situations where the caller already has the result of an expensive method call, and wants to preset that result as memoized for future calls. In other words, the memoized method will be called zero times rather than once.

NOTE: Currently, no attempt is made to validate that the given arguments are valid for the given method.

Examples:

class Example
  prepend MemoWise
  attr_reader :method_called_times

  def method_to_preset
    @method_called_times = (@method_called_times || 0) + 1
    "A"
  end
  memo_wise :method_to_preset
end

ex = Example.new

ex.preset_memo_wise(:method_to_preset) { "B" }

ex.method_to_preset #=> "B"

ex.method_called_times #=> nil

Parameters:

  • method_name (Symbol)

    Name of a method previously setup with #memo_wise.

  • args (Array)

    (Optional) If the method takes positional args, these are the values of position args for which the given block's result will be preset as the memoized result.

  • kwargs (Hash)

    (Optional) If the method takes keyword args, these are the keys and values of keyword args for which the given block's result will be preset as the memoized result.

Yield Returns:

  • (Object)

    The result of the given block will be preset as memoized for future calls to the given method.


325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/memo_wise.rb', line 325

def preset_memo_wise(method_name, *args, **kwargs)
  validate_memo_wised!(method_name)

  unless block_given?
    raise ArgumentError,
          "Pass a block as the value to preset for #{method_name}, #{args}"
  end

  validate_params!(method_name, args)

  if method(method_name).arity.zero?
    @_memo_wise[method_name] = yield
  else
    @_memo_wise[method_name][fetch_key(method_name, *args, **kwargs)] = yield
  end
end

#reset_memo_wise(method_name = nil, *args, **kwargs) ⇒ void

This method returns an undefined value.

Resets memoized results of a given method, or all methods.

There are three reset modes depending on how this method is called:

method + args mode (most specific)

  • If given method_name and either args or kwargs or both:
  • Resets only the memoized result of calling method_name with those particular arguments.

method (any args) mode

  • If given method_name and neither args nor kwargs:
  • Resets all memoized results of calling method_name with any arguments.

all methods mode (most general)

  • If not given method_name:
  • Resets all memoized results of calling all methods.

Examples:

class Example
  prepend MemoWise

  def method_to_reset(x)
    @method_called_times = (@method_called_times || 0) + 1
  end
  memo_wise :method_to_reset
end

ex = Example.new

ex.method_to_reset("a") #=> 1
ex.method_to_reset("a") #=> 1

ex.method_to_reset("b") #=> 2
ex.method_to_reset("b") #=> 2

ex.reset_memo_wise(:method_to_reset, "a") # reset "method + args" mode

ex.method_to_reset("a") #=> 3
ex.method_to_reset("a") #=> 3

ex.method_to_reset("b") #=> 2
ex.method_to_reset("b") #=> 2

ex.reset_memo_wise(:method_to_reset) # reset "method" (any args) mode

ex.method_to_reset("a") #=> 4
ex.method_to_reset("b") #=> 5

ex.reset_memo_wise # reset "all methods" mode

Parameters:

  • method_name (Symbol, nil) (defaults to: nil)

    (Optional) Name of a method previously setup with #memo_wise. If not given, will reset all memoized results for all methods.

  • args (Array)

    (Optional) If the method takes positional args, these are the values of position args for which the memoized result will be reset.

  • kwargs (Hash)

    (Optional) If the method takes keyword args, these are the keys and values of keyword args for which the memoized result will be reset.


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/memo_wise.rb', line 409

def reset_memo_wise(method_name = nil, *args, **kwargs)
  if method_name.nil?
    unless args.empty?
      raise ArgumentError, "Provided args when method_name = nil"
    end

    unless kwargs.empty?
      raise ArgumentError, "Provided kwargs when method_name = nil"
    end

    return @_memo_wise.clear
  end

  unless method_name.is_a?(Symbol)
    raise ArgumentError, "#{method_name.inspect} must be a Symbol"
  end

  unless respond_to?(method_name)
    raise ArgumentError, "#{method_name} is not a defined method"
  end

  validate_memo_wised!(method_name)

  if args.empty? && kwargs.empty?
    @_memo_wise.delete(method_name)
  else
    @_memo_wise[method_name].delete(fetch_key(method_name, *args, **kwargs))
  end
end