[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Associating data with a function

Gavin Kistner

10/4/2006 11:18:00 PM

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( )
} )

24 Answers

Jano Svitok

10/4/2006 11:59:00 PM

0

Just two ideas, it's too late to actually think ;-)

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

dblack

10/5/2006 12:32:00 AM

0

Joel VanderWerf

10/5/2006 1:02:00 AM

0

Gavin Kistner wrote:
> 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

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

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Nobuyoshi Nakada

10/5/2006 1:05:00 AM

0

Hi,
At Thu, 5 Oct 2006 08:18:24 +0900,
Gavin Kistner wrote in [ruby-talk:218056]:
> 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

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

--
Nobu Nakada

Joel VanderWerf

10/5/2006 1:07:00 AM

0

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.

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Ara.T.Howard

10/5/2006 5:34:00 AM

0

Robert Klemme

10/5/2006 8:49:00 AM

0

On 05.10.2006 01:18, Gavin Kistner wrote:
> 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

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

Verno Miller

10/5/2006 9:25:00 AM

0

>Robert Klemme wrote:
> On 05.10.2006 01:18, Gavin Kistner wrote:
>
> I can think of several solutions.
>
> 1. Use command pattern, ...
> ...
> 2. Use factory with a pool ...
>

...

3. Flyweight design pattern

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

Cheers,
verno

--
Posted via http://www.ruby-....

dblack

10/5/2006 9:58:00 AM

0

Trans

10/5/2006 11:04:00 AM

0


Gavin Kistner wrote:
> 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

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.