Methodhash 0.5 released

I created a small "lab tool" that makes life easier for me. It's
basically a Hash that you can't put values into, the values can only
be obtained from a method that you define. I created it with the idea
that a calculation should only have to be done once. I paste the
README file below.
Please let me know what you think!

Best regards,
Fredrik Johansson

NAME

  methodhash

SYNOPSIS

  A Hash subclass for automatic storage of values obtained from a
method
  defined by the user. Useful for lengthy calculations on large
datasets.

URI

  http://github.com/fredrikj/methodhash

INSTALL

  gem install methodhash

DESCRIPTION

  A Hash subclass that defines its values from a specified method.
  Use it by creating a subclass of MethodHash, and define a method
  with the name "mymethod" in it.
  Like this (same code in samples/samples.rb):

  # 1. Simple use
  class AddOne < MethodHash
    def mymethod(a)
      sleep 3
      a + 1
    end
  end

  a = AddOne.new
  a # {}
  a[1] # 2
  a[7] # 8
  a # {1=>2, 7=>8}
  puts a.dump # --- !map:AddOne
               # 1: 2
               # 7: 8

  # 2. With a file
  b = AddOne.new '/tmp/one.yml'
  b # {}
  b[1] # 2
  b.dump # '/tmp/one.yml'
  c = AddOne.new '/tmp/one.yml'
  puts c.inspect # {1=>2}

  # 3. Some protection against data corruption.
  class AddTwo < MethodHash
    def mymethod(a)
      a + 2
    end
  end

  begin
    d = AddTwo.new '/tmp/one.yml' # ArgumentError: Path holds class
AddOne
  rescue
    puts $!
  end

  # 4. Saving exceptions arising from mymethod.
  class AddOneFaulty < MethodHash
    def mymethod(a)
      rand(2)==0 ? raise("Epic Fail!") : a+1
    end
  end

  e = AddOneFaulty.new
  e[1] # RuntimeError: Epic Fail! # If something bad
happened
  e # {1=>"ERROR: Epic Fail!"}
  e.retry_errors # false
  e[1] # RuntimeError: Epic Fail! # Still keeping
the error
  e.retry_errors=true # true
  e[1] # 2 # If better luck
this time
  e # {1=>2}

  # 5. A more complex setting
  class AddThree < MethodHash
    def initialize(path1=nil, path2=nil, mypath=nil)
      @one = AddOne.new(path1)
      @two = AddTwo.new(path2)
      super(mypath)
    end

    def mymethod(a)
      @one[a] + @two[a] - a
    end

    def dump
      @one.dump
      @two.dump
      super
    end
  end

  f = AddThree.new( '/tmp/one.yml', '/tmp/two.yml')
  puts f[3]
  f.dump

  # 6. With two arguments
  class Add < MethodHash
    def mymethod(a,b)
      a + b
    end
  end

HISTORY
  0.5.0

    Initial version

I created a small "lab tool" that makes life easier for me. It's
basically a Hash that you can't put values into, the values can only
be obtained from a method that you define. I created it with the idea
that a calculation should only have to be done once. I paste the
README file below.
Please let me know what you think!

http://raa.ruby-lang.org/project/memoize/

  A Hash subclass that defines its values from a specified method.
  Use it by creating a subclass of MethodHash, and define a method
  with the name "mymethod" in it.
  Like this (same code in samples/samples.rb):

  # 1. Simple use
  class AddOne < MethodHash
    def mymethod(a)
      sleep 3
      a + 1
    end
  end

  a = AddOne.new
  a # {}
  a[1] # 2
  a[7] # 8
  a # {1=>2, 7=>8}
  puts a.dump # --- !map:AddOne
               # 1: 2
               # 7: 8

You can do this with a Hash already:

irb(main):001:0> a = Hash.new {|h,k| printf("calc %p\n",k);h[k] = k + 2} # add two
=> {}
irb(main):002:0> a[1]
calc 1
=> 3
irb(main):003:0> a[1]
=> 3
irb(main):004:0> a[5]
calc 5
=> 7
irb(main):005:0> a[5]
=> 7
irb(main):006:0> a
=> {1=>3, 5=>7}
irb(main):007:0>

Granted, serialization to a file needs a few lines more.

Kind regards

  robert

···

On 02/04/2010 12:46 PM, Fredrik Johansson wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

You can do this with a Hash already:

Sure (I learned that from Ruby Best Practices. great book!), but I
would like to argue that it's a bit more tidy to define a class (a
subclass of MethodHash) in one place, which I can then reuse for
anything I need to. But I don't claim to have revolutionized the world
with this. :slight_smile:

Granted, serialization to a file needs a few lines more.

The use I have of it is that some calculations I have made that took
some time to do, is easily available at any time later by just
remembering the class name I used for it. Maybe the name MethodHash is
misleading...

Doing so introduces an inheritance relationship, which is rarely a
good thing in Ruby.
You might want to check out the link Robert suggested, for memoize.

http://raa.ruby-lang.org/project/memoize/

It also handles file based caching and is one idiomatic way of doing
what you're trying to do here.

-greg

···

On Thu, Feb 4, 2010 at 8:05 AM, Fredrik Johansson <fredjoha@gmail.com> wrote:

You can do this with a Hash already:

Sure (I learned that from Ruby Best Practices. great book!), but I
would like to argue that it's a bit more tidy to define a class (a
subclass of MethodHash) in one place, which I can then reuse for
anything I need to. But I don't claim to have revolutionized the world
with this. :slight_smile:

You can do this with a Hash already:

Sure (I learned that from Ruby Best Practices. great book!), but I
would like to argue that it's a bit more tidy to define a class (a
subclass of MethodHash) in one place, which I can then reuse for
anything I need to.

Well, there are multiple ways to reuse code and maintain DRY. You do not necessarily need a class for that.

def add_two
   Hash.new {|h,k| h[k] = k + 2}
end

or, a more generic variant

def make_cache
   Hash.new {|h,k| h[k] = yield(k)}
end

AddTwo = make_cache {|x| x + 2}

etc.

But I don't claim to have revolutionized the world
with this. :slight_smile:

Well, if you put a library in public then you probably want it to be used. For that it needs to provide value to others. If it doesn't it's probably not used and your effort may be in vain. :slight_smile:

The use I have of it is that some calculations I have made that took
some time to do, is easily available at any time later by just
remembering the class name I used for it. Maybe the name MethodHash is
misleading...

But what you actually want is the *instance* that does the caching, otherwise you'll loose cached values. Or do you store results somewhere in a class instance variable?

Kind regards

  robert

···

On 04.02.2010 14:02, Fredrik Johansson wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/