Associating data with a function

Because I just had to solve this problem in both JavaScript and Lua, and
because I love Ruby more than either, I thought I’d ask people here how
they’d solve it in Ruby.

The general question is: how would do you associate a few object
instances with methods on a per-method basis?

The Situation/Requirements

A class has 2 different methods.
Each method needs a couple ‘scratch’ objects to perform its
calculations.
The methods call each other; they must not use the same scratch objects.
It is expensive to instantiate a scratch object.
It’s not expensive to initialize an existing scratch object with data.
We like OOP coding, and want to call these as methods of a receiver.

Wasteful Example:
class Foo
def c1
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

The Solution We’ll Ignore

We could create a distinct instance variable for each needed variable.
I personally don’t like the weird coupling this creates, though. So I’m
ignoring it.

class Foo
def initialize
@tmp1 = ExpensiveObject.new
@tmp2 = ExpensiveObject.new
@tmp3 = ExpensiveObject.new
@tmp4 = ExpensiveObject.new
end
def c1
tmp1, tmp2 = @tmp1, @tmp2
#…
end
def c2
tmp1, tmp2 = @tmp3, @tmp4
#…
end
end

The JavaScript Solution

The solution I used for JavaScript was that functions/methods are
first-class objects that can have arbitrary data assigned, and can get a
reference to themselves during execution. So:

Foo.prototype.c1 = function( ) {
var tmp1 = arguments.callee.eo1;
var tmp2 = arguments.callee.eo2;
//…
}
Foo.prototype.c1.eo1 = new ExpensiveObject;
Foo.prototype.c1.eo2 = new ExpensiveObject;

There were a few potential solutions for Lua. I’ll list the two best,
just to help give you ideas:

Lua Solution #1: Closures (would work for JS, too)

Foo.c1 = ( function( )
local tmp1 = ExpensiveObject:new( )
local tmp2 = ExpensiveObject:new( )
return function( self )
– stuff using tmp1 and tmp2
end
end)( )

Lua Solution #2: Changing the global environment for a function

function associateFunctionData( inF, inData )
setfenv(inF, setmetatable(inData,{__index=getfenv(inF)}))
end

function Foo:c1( )
– stuff using tmp1 and tmp2
– as though they were globals
end

associateFunctionData( Foo.c1, {
tmp1 = ExpensiveObject:new( ),
tmp2 = ExpensiveObject:new( )
} )

Hi –

On Thu, 5 Oct 2006, Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
tmp2 = ExpensiveObject.new

Lua Solution #1: Closures (would work for JS, too)

Foo.c1 = ( function( )
local tmp1 = ExpensiveObject:new( )
local tmp2 = ExpensiveObject:new( )
return function( self )
– stuff using tmp1 and tmp2
end
end)( )

You could do:

class Foo
def initialize
tmp1, tmp2 = EO.new, EO.new
define_method(“c1”) { do stuff with tmp1 etc. }
tmp1, tmp2 = EO.new, EO.new
define_method(“c2”) { do stuff with tmp1 etc. }
end
end

or something like that. I’d be inclined to revisit the instance
variable version, though. I’m at the bottom of the well of mental
energy right now or I’d try to come up with a twist on it that might
seem less coupled…

David

Just two ideas, it’s too late to actually think :wink:

you can change your ‘ignored’ vesion to something more acceptable:


def c1
@c1_tmp1 ||= ExpensiveObject.new
@c1_tmp2 ||= ExpensiveObject.new
tmp1, tmp2 = @c1_tmp1, @c2.tmp2

end

I was thinking of some kind of delegation, but it got too messy so I
abandoned it.
Another possibility is to implement a pool of objects directly into
ExpensiveObject or to create a layer above it.

i.e. something like (not tested, not thread-safe, etc.):

class ExtensiveObjectPool
class << self
def initialize
@idle, @busy = [], []
end

 def with(how_many)
     objs = allocate(how_many)
     yield *objs
 ensure
     release(objs)
 end

 def allocate(num)
      ret = []
      while num > 0 and not @free.empty?
          num -= 1
          ret << @free.pop
          @busy << ret.last
      end
      while num > 0
          num -= 1
          ret << ExpensiveObject.new
          @busy << ret.last
      end
      ret
 end
 def release(objs)
     @busy.delete(*objs)
     @free += objs
 end

end

class Foo
def c1
ExtensiveObjectPool.with(2) do |tmp1, tmp2|
result = tmp1 + tmp2 + c2
end
end

def c2
ExtensiveObjectPool.with(2) do |tmp1, tmp2|
result = tmp1 + tmp2
end
end
end

Hi,
At Thu, 5 Oct 2006 08:18:24 +0900,
Gavin K. wrote in [ruby-talk:218056]:

Wasteful Example:
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

Like this?

$ ruby eo.rb 5
#<Foo::ExpensiveObject: 1>
#<Foo::ExpensiveObject: 2>
#<Foo::ExpensiveObject: 3>
#<Foo::ExpensiveObject: 4>
#<Foo::ExpensiveObject: 5>
[:c1,
#<Foo::ExpensiveObject: 1>,
#<Foo::ExpensiveObject: 2>,
[:c2,
#<Foo::ExpensiveObject: 3>,
#<Foo::ExpensiveObject: 4>,
#<Foo::ExpensiveObject: 5>,
[:c1,
#<Foo::ExpensiveObject: 1>,
#<Foo::ExpensiveObject: 2>,
[:c2,
#<Foo::ExpensiveObject: 3>,
#<Foo::ExpensiveObject: 4>,
#<Foo::ExpensiveObject: 5>,
[:c1, #<Foo::ExpensiveObject: 1>, #<Foo::ExpensiveObject: 2>,
[]]]]]]

$ cat eo.rb
#!/usr/bin/ruby

module ScratchArgument
def scratch_method(name)
meth = instance_method(name)
define_method(name) do |*args0|
*eo = *yield
imeth = meth.bind(self)
(class << self; self; end).class_eval do
define_method(name) do |*args|
args.unshift(*eo)
imeth.call(*args)
end
end
args0.unshift(*eo)
imeth.call(*args0)
end
end
end

class Foo
extend ScratchArgument

class ExpensiveObject
@@count = 0
def initialize
@id = @@count += 1
p self
end
def inspect
“#<#{self.class.name}: #{@id}>”
end
end

def c1(tmp1, tmp2, i)
if i <= 0
[]
else
[:c1, tmp1, tmp2, c2(i - 1)]
end
end
scratch_method(:c1) do
[ExpensiveObject.new, ExpensiveObject.new]
end

def c2(tmp1, tmp2, tmp3, i)
if i <= 0
[]
else
[:c2, tmp1, tmp2, tmp3, c1(i - 1)]
end
end
scratch_method(:c2) do
[ExpensiveObject.new, ExpensiveObject.new, ExpensiveObject.new]
end
end

result = Foo.new.c1(ARGV.empty? ? 3 : ARGV.first.to_i)
require ‘pp’
pp result

Joel VanderWerf wrote:

Use closures and class scopes:

class Foo
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new

Assuming you want to share the tmp1 and tmp2 among all instances of the
class, which probably you don’t want. Sorry.

Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

Use closures and class scopes:

class Foo
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new

define_method :c1 do
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
end

class Foo
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new

define_method :c2 do
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

On Thu, 5 Oct 2006, Gavin K. wrote:

objects to perform its calculations. The methods call each other; they must
result = tmp1 + tmp2 + c2
The Solution We’ll Ignore
@tmp4 = ExpensiveObject.new
end
def c1
tmp1, tmp2 = @tmp1, @tmp2
#…
end
def c2
tmp1, tmp2 = @tmp3, @tmp4
#…
end
end

what’s wrong with the simple solution:

class Foo
METHOD_DATA = {
‘c1’ => {
‘tmp1’ => ExpensiveObject.new,
‘tmp2’ => ExpensiveObject.new,
},

   'c2' => {
     'tmp1' => ExpensiveObject.new,
     'tmp2' => ExpensiveObject.new,
   },
 }

def mdata m, key
  METHOD_DATA[m.to_s][key.to_s]
end

def c1
  tmp1, tmp2 = mdata('c1', 'tmp1'), mdata('c1', 'tmp2')
  result = tmp1 + tmp2 + c2
end

def c2
  tmp1, tmp2 = mdata('c2', 'tmp1'), mdata('c2', 'tmp2')
  result = tmp1 + tmp2
end

end

you can make it lazy using lambda if you want…

??

cheers.

-a

Robert K. wrote:
On 05.10.2006 01:18, Gavin K. wrote:

I can think of several solutions.

  1. Use command pattern, …
  2. Use factory with a pool …

  1. Flyweight design pattern

http://redcorundum.blogspot.com/2006/09/simple-flyweight-implementation_26.html

Cheers,
verno

On 05.10.2006 01:18, Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

I can think of several solutions.

  1. Use command pattern, i.e., distribute method c1 and c2 to different
    classes (i.e. a class per method), make your local variables instance
    variables and reuse instances of these command classes. If you have
    common behavior or state you can even use a common base class or a
    Module.

  2. Use factory with a pool to create and release objects whose creation
    is expensive. The factory can reside at class level. Gotcha: if you do
    multithreading you have to make sure the factory deals properly with
    that.

Note, both approaches work with pretty much every OO language and do not
need any kind of fancy environment changes or closures.

Kind regards

robert

Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

I look at this and think, “where the persistant locals?” Well, we don’t
have em but they can be emulatated via instance vars.

class Foo
def c1
tmp1 = (@_tmp1 ||= ExpensiveObject.new)
tmp2 = (@_tmp2 ||= ExpensiveObject.new)
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = (@_tmp1 ||= ExpensiveObject.new)
tmp2 = (@_tmp2 ||= ExpensiveObject.new)
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

T.

Hi –

On Thu, 5 Oct 2006, [email protected] wrote:

variable version, though. I’m at the bottom of the well of mental
energy right now or I’d try to come up with a twist on it that might
seem less coupled…

See Joel’s response for how to do this correctly :slight_smile: (i.e., calling
define_method in top-level class definition scope, not in an instance
method where it won’t have any meaning)

David

[email protected] wrote:

     'tmp1' => ExpensiveObject.new,
     'tmp2' => ExpensiveObject.new,
   },
 }

It’s certainly effective, and slightly nicer than instance variables
since it clearly describes which methods the variables go with. I’m not
wild about it just because it means that when you edit the method you
need to remember to go edit a data structure somewhere else.

(This is one reason that makes a separately-managed pool much nicer
than closure or other solutions where you must instantiate the objects
outside of the method itself.)

A viable alternative nonetheless.

Trans wrote:

def c2
tmp1 = (@_tmp1 ||= ExpensiveObject.new)
tmp2 = (@_tmp2 ||= ExpensiveObject.new)
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

Persistent locals is an interesting concept. Your specific example
fails, however, if I change c1’s last line to:
result = tmp1 * c2 + tmp2
because calculating c2 will change the value in @_tmp2, making the tmp2
in c1 invalid. You’d need to ensure that each instance variable had a
unique name in each function, which is just about the same as declaring
all the instance variables during initialization or class creation.

Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2 + c2
end
def c2
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
# stuff involving tmp1/tmp2
result = tmp1 + tmp2
end
end

Hmm then again…

require ‘facet/annotation’

class Foo

ann :c1, :tmp1 => ExpensiveObject.new,
              :tmp2 => ExpensiveObject.new

def c1
  result = ann.c1.tmp1 + ann.c1.tmp2 + c2
end

ann :c2, :tmp1 => ExpensiveObject.new,
              :tmp2 => ExpensiveObject.new

def c2
  result = ann.c2.tmp1 + ann.c2.tmp2
end

end

Works as long ExpensiveObject isn’t dependent on instance context.
(Note: If anyone actually tries this it may need to use:
‘self.class.ann.c1.tmp1’ instead, although it ought to work as given.)

T.

Nobuyoshi N. wrote:

    end
  end
  args0.unshift(*eo)
  imeth.call(*args0)
end

end
end

Wow, that’s pretty crazy, Nobu! Thanks for the brain bending. :slight_smile:

On Thu, Oct 05, 2006 at 08:18:24AM +0900, Gavin K. wrote:

Each method needs a couple ‘scratch’ objects to perform its
local tmp2 = ExpensiveObject:new( )
return function( self )
– stuff using tmp1 and tmp2
end
end)( )

Can’t you do the same here?

class A
tmp1 = ExpensiveObject.new
tmp2 = ExpensiveObject.new
define_method(:c1) do # Look ma! a closure

stuff using tmp1 and tmp2

end
end

Phrogz wrote:

end
result = tmp1 * c2 + tmp2
because calculating c2 will change the value in @_tmp2, making the tmp2
in c1 invalid. You’d need to ensure that each instance variable had a
unique name in each function, which is just about the same as declaring
all the instance variables during initialization or class creation.

Right, they should have had differnt names.

T.

Logan C. wrote:

On Thu, Oct 05, 2006 at 08:18:24AM +0900, Gavin K. wrote:

Lua Solution #1: Closures (would work for JS, too)
Can’t you do the same here?
[snip]

Yup - both David Black and Joel VanderWerf posted that same solution
yesterday :slight_smile:

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs