i seems to recall someone came up with an impl of instance_exec (instance_eval that takes args) a while back - might have even been me! ;-) anyone remember? -a
on 07.07.2006 19:24
on 07.07.2006 19:30
On Sat, Jul 08, 2006 at 02:22:31AM +0900, ara.t.howard@noaa.gov wrote: > > i seems to recall someone came up with an impl of instance_exec > (instance_eval > that takes args) a while back - might have even been me! ;-) anyone > remember? There is one in Rails' ActiveSupport: class Object unless defined? instance_exec # 1.9 def instance_exec(*arguments, &block) block.bind(self)[*arguments] end end end class Proc def bind(object) block, time = self, Time.now (class << object; self end).class_eval do method_name = "__bind_#{time.to_i}_#{time.usec}" define_method(method_name, &block) method = instance_method(method_name) remove_method(method_name) method end.bind(object) end end marcel
on 07.07.2006 19:33
On 7/7/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote: > > i seems to recall someone came up with an impl of instance_exec (instance_eval > that takes args) a while back - might have even been me! ;-) anyone remember? > > -a > -- You did indeed! And so did I. However, Mauricio Fernandez came up with what seems to be a fairly complete implementation (thread safe, handles frozen objects, etc.) at: http://eigenclass.org/hiki.rb?cmd=view&p=instance_exec&key=instance_exec Regards, Sean
on 07.07.2006 19:40
On Sat, 8 Jul 2006, Marcel Molina Jr. wrote: > unless defined? instance_exec # 1.9 > method_name = "__bind_#{time.to_i}_#{time.usec}" > define_method(method_name, &block) > method = instance_method(method_name) > remove_method(method_name) > method > end.bind(object) > end > end thanks! i just hacked this one out: harp:~ > cat a.rb class Object unless instance_methods.include? 'instance_exec' def instance_exec *a, &b m = "__instance_exec_#{ Thread.current.object_id.abs }__" singleton_class{ define_method m, &b } send m, *a ensure singleton_class{ undef_method m } rescue nil end end unless instance_methods.include? 'singleton_class' def singleton_class &b sc = class << self; self; end sc.module_eval &b if b sc end end end a = [] a.instance_exec 42 do |elem| push elem end p a harp:~ > ruby a.rb [42] regards. -a
on 07.07.2006 19:51
On 7/7/06, Marcel Molina Jr. <marcel@vernix.org> wrote: > unless defined? instance_exec # 1.9 > method_name = "__bind_#{time.to_i}_#{time.usec}" > Marcel Molina Jr. <marcel@vernix.org> > > This is not thread-safe - relying on time.usec may not work on sufficiently fast or clock-impaired machines. Regards, Sean
on 07.07.2006 20:25
Marcel Molina Jr. wrote: > unless defined? instance_exec # 1.9 > method_name = "__bind_#{time.to_i}_#{time.usec}" > define_method(method_name, &block) > method = instance_method(method_name) > remove_method(method_name) > method > end.bind(object) > end > end Huh. That looks like the one I wrote a while back (minus the time stamp). Mauricio Fernandez's looks better though --think I'll have to rip it for Facets ;-) T.
on 08.07.2006 04:14
On 7/7/06, transfire@gmail.com <transfire@gmail.com> wrote: > Huh. That looks like the one I wrote a while back (minus the time > stamp). > > Mauricio Fernandez's looks better though --think I'll have to rip it > for Facets ;-) > > T. > Do - it's the best version for 1.8 so far (IMHO). Regards, Sean
on 09.07.2006 08:11
On Sat, 8 Jul 2006 transfire@gmail.com wrote: > Mauricio Fernandez's looks better though --think I'll have to rip it > for Facets ;-) i just came across a bug in it (it's not re-entrant) - here's the fix: harp:~ > cat a.rb class Object unless instance_methods.include? 'instance_exec' def instance_exec *a, &b m, n = nil, -1 loop{ m = "__instance_exec_#{ Thread.current.object_id.abs }_#{ n += 1 }__" break unless respond_to? m } singleton_class{ define_method m, &b } send m, *a ensure singleton_class{ undef_method m } end end unless instance_methods.include? 'singleton_class' def singleton_class &b sc = class << self; self; end sc.module_eval &b if b sc end end end a = [] a.instance_exec 42 do |x| instance_exec x do |y| push y p size end end harp:~ > ruby a.rb 1 figured you'd use this if anyone would tom... cheers. -a
on 09.07.2006 10:16
On Sun, Jul 09, 2006 at 03:09:52PM +0900, ara.t.howard@noaa.gov wrote: > >Mauricio Fernandez's looks better though --think I'll have to rip it > >for Facets ;-) > > i just came across a bug in it (it's not re-entrant) - here's the fix: > [#instance_exec implementation] The example you gave does work with my implementation too: RUBY_VERSION # => "1.8.5" RUBY_RELEASE_DATE # => "2006-06-24" class Object module InstanceExecHelper; end include InstanceExecHelper def instance_exec(*args, &block) mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}" InstanceExecHelper.module_eval{ define_method(mname, &block) } begin ret = send(mname, *args) ensure InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil # ========== # thx to this end ret end end a = [] a.instance_exec 42 do |x| instance_exec x do |y| push y p size end end a # => [42] a.size # => 1 # >> 1 I had actually a test for that (see test_instance_exec_nested below). Do you have any additional assertions handy? (So far test_instance_exec_with_frozen_obj fails in all the other implementations I've seen.) require 'test/unit' class TestInstanceEvalWithArgs < Test::Unit::TestCase def test_instance_exec # Create a block that returns the value of an argument and a value # of a method call to +self+. block = lambda { |a| [a, f] } assert_equal [:arg_value, :dummy_value], Dummy.new.instance_exec(:arg_value, &block) end def test_instance_exec_with_frozen_obj block = lambda { |a| [a, f] } obj = Dummy.new obj.freeze assert_equal [:arg_value, :dummy_value], obj.instance_exec(:arg_value, &block) end def test_instance_exec_nested i = 0 obj = Dummy.new block = lambda do |arg| [arg] + instance_exec(1){|a| [f, a] } end # the following assertion expanded by the xmp filter automagically from: # obj.instance_exec(:arg_value, &block) #=> assert_equal([:arg_value, :dummy_value, 1], obj.instance_exec(:arg_value, &block)) end end
on 09.07.2006 13:07
Thanks Ara. Do you have a testcase I can use? T.
on 10.07.2006 12:14
> The example you gave does work with my implementation too: > > RUBY_VERSION # => "1.8.5" > RUBY_RELEASE_DATE # => "2006-06-24" > class Object > module InstanceExecHelper; end > include InstanceExecHelper > def instance_exec(*args, &block) > mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}" If instance_exec is called recursively, like in the example, the mname for the outer method is reused for defining the inner method. This isn't really a problem, but conceptually not very "clean"... Did it work "by accident", or did you really intent to reuse the name? ;] > InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil This "rescue nil" might have been an indication of this "bad design"... ;] I wonder, why did you use a module for the context, instead of the good old meta class? gegroet, Erik V. - http://www.erikveen.dds.nl/
on 10.07.2006 12:36
Okay, criticizing somebody's work without providing a "solution", isn't nice at all... So, here's my version... gegroet, Erik V. - http://www.erikveen.dds.nl/ PS: I'm still curious about the reason for using a module. ---------------------------------------------------------------- class Object def instance_exec(*args, &block) object = self res = nil class << self self end.instance_eval do begin Thread.critical = true @_instance_exec_count_ ||= 0 @_instance_exec_count_ += 1 method_name = "_instance_exec_#{@_instance_exec_count_}_" ensure Thread.critical = false end begin define_method(method_name, &block) res = object.send(method_name, *args) ensure remove_method(method_name) rescue nil end end res end end
on 10.07.2006 13:32
Erik Veenstra wrote: > Okay, criticizing somebody's work without providing a > "solution", isn't nice at all... > > So, here's my version... > > gegroet, > Erik V. - http://www.erikveen.dds.nl/ > > PS: I'm still curious about the reason for using a module. I think the idea was to save frm creting a singleton class, but at the expense of a module I don't see why either. Anyway, here the version I put in Facets. Please evaluate. module Kernel # Like instace_eval but allows parameters to be passed. # # The implementation is thread-safe thanks to the # Thread.current.object_id trick, but it doesn't work with # immediate values (Fixnums and friends), and most # importantly it bombs when given a frozen object. def instance_exec(*args, &block) mname = "__instance_exec(#{Thread.current.object_id},#{caller.object_id})" Object.class_eval{ define_method(mname, &block) } begin ret = send(mname, *args) ensure Object.class_eval{ undef_method(mname) } end ret end end
on 10.07.2006 14:28
> Anyway, here the version I put in Facets. Please evaluate. > > # The implementation is thread-safe thanks to the > # Thread.current.object_id trick, but it doesn't work with > # immediate values (Fixnums and friends),... (You're version _does_ work with immediate versions.) Mmh... The Module version does work for immediate versions, too. That might be a good reason... ;] Mine doesn't... > # ...and most > # importantly it bombs when given a frozen object. > > Object.class_eval{ define_method(mname, &block) } This pollutes the global class Object. Not funny... ;] But, yes, it does work... The Module versions only pollutes the class Object with one, dedicated Module. That's not too bad. If we want to handle immediate values and we can't create singleton classes for them, we have to abuse something. Maybe, after all, the Module version isn't that bad... ;] gegroet, Erik V. - http://www.erikveen.dds.nl/
on 10.07.2006 15:14
Erik Veenstra wrote: > > Mine doesn't... > > > # ...and most > > # importantly it bombs when given a frozen object. > > > > Object.class_eval{ define_method(mname, &block) } > > This pollutes the global class Object. Not funny... ;] But, > yes, it does work... It should delete it when done, so it's a temporary pollution. But I think to do that right, instead of undef_method that should be remove_method: Object.class_eval{ remove_method(mname) } Good catch thanks. T.
on 10.07.2006 15:18
On Mon, 10 Jul 2006 transfire@gmail.com wrote: >> PS: I'm still curious about the reason for using a module. > # Thread.current.object_id trick, but it doesn't work with > Object.class_eval{ undef_method(mname) } > end > ret > end > > end -a
on 10.07.2006 15:48
Duh! I'm sitting here screwing with this and the answer's sitting one
directory over.. with only a minor modification (I added recv=nil) I
introduce you to Facets' Proc#to_method:
#--
# Credit for #to_method goes to Forian Gross (flgr).
#--
require 'thread'
class Proc
MethodMutexes = Hash.new do |hash, key|
hash[key] = Mutex.new
end
# Creates a local method based on a Proc.
def to_method(name=nil, recv=nil)
name ||= "!to_method_temp#{self.object_id}"
recv ||= eval("self", self)
klass = recv.class
MethodMutexes[klass => name].synchronize do
begin
klass.send(:define_method, name, &self)
return recv.method(name)
ensure
klass.send(:remove_method, name) if not name
end
end
end
end
Then instance_exec is simply:
module Kernel
def instance_exec(*args, &block)
block.to_method(nil,self).call(*args)
end
end
Proof is in the pudding. Facets' really does pay off!
T.
on 10.07.2006 16:15
On Mon, Jul 10, 2006 at 08:29:46PM +0900, transfire@gmail.com wrote: > > PS: I'm still curious about the reason for using a module. * it allows instance_exec to work on frozen objects and immediate values * it doesn't pollute Object with several methods (even if temporarily), only one module > # immediate values (Fixnums and friends), and most > ret > end > > end heh you copied the comment from my blog entry[1] but that's not the implementation it applied to ;). The above does work with immediate values and frozen objects. But I now consider it flawed nonetheless, read http://eigenclass.org/hiki.rb?bounded+space+instance_exec [1] http://eigenclass.org/hiki.rb?instance_exec
on 10.07.2006 16:59
On Mon, Jul 10, 2006 at 10:44:32PM +0900, transfire@gmail.com wrote: > Duh! I'm sitting here screwing with this and the answer's sitting one > directory over.. with only a minor modification (I added recv=nil) I > introduce you to Facets' Proc#to_method: > > #-- > # Credit for #to_method goes to Forian Gross (flgr). (Florian) > # Creates a local method based on a Proc. > def to_method(name=nil, recv=nil) > name ||= "!to_method_temp#{self.object_id}" ================================ This can create a lot of different symbols, leading to large memory leaks. > recv ||= eval("self", self) > klass = recv.class > MethodMutexes[klass => name].synchronize do ============================ This makes the memleak even heavier, going from potentially ~70 bytes to nearly 300 bytes per call... > begin > klass.send(:define_method, name, &self) ========== This will fail if the class was frozen. See my blog for a better implementation.
on 10.07.2006 16:59
On Mon, Jul 10, 2006 at 07:33:34PM +0900, Erik Veenstra wrote: > Okay, criticizing somebody's work without providing a > "solution", isn't nice at all... nah, what matters is *improving* the code (and focusing on it) > PS: I'm still curious about the reason for using a module. As I said, it reduces pollution and makes it work with frozen and immediate values. See the unit tests and the explanation at http://eigenclass.org/hiki.rb?instance_exec. > @_instance_exec_count_ ||= 0 ====================== adds an instance variable for each object to which we send the instance_exec message (fails for frozen objects, pollutes and costs more memory) > @_instance_exec_count_ += 1 > method_name = "_instance_exec_#{@_instance_exec_count_}_" > ensure > Thread.critical = false ======================= The old Thread.critical value is lost, so this could easily break multi-threaded code... It should be begin old, Thread.critical = Thread.critical, true ... ensure Thread.critical = old end > end > > begin > define_method(method_name, &block) > > res = object.send(method_name, *args) > ensure > remove_method(method_name) rescue nil > end > end All in all, this implementation is the second best one as far as space efficiency is concerned ;) I don't like having to point to my own site repeatedly, but you should really have a look at http://eigenclass.org/hiki.rb?bounded+space+instance_exec So far all the implementations I've seen posted were worse than the one shown there (and than the original one in my older blog entry). It's the only thread-safe, reentrant, bounded-space, frozen-object-safe implementation as far as I know.
on 10.07.2006 17:26
Mauricio Fernandez wrote: > On Mon, Jul 10, 2006 at 10:44:32PM +0900, transfire@gmail.com wrote: > This can create a lot of different symbols, leading to large memory leaks. > This makes the memleak even heavier, going from potentially ~70 bytes to > nearly 300 bytes per call... > This will fail if the class was frozen. Right. I expected as much. But can you apply yuor fixes to Proc#to_method? T.
on 10.07.2006 17:35
> Mauricio Fernandez wrote: > This will fail if the class was frozen. Actually, it seems to pass the frozen test. T.
on 10.07.2006 18:24
Okay, I think I've improved it some more:
require 'thread'
class Proc
MethodMutexes = Hash.new do |hash, key|
hash[key] = Mutex.new
end
# Creates a local method based on a Proc.
def to_method(name=nil, recv=nil)
unless name
n = 0
n += 1 while recv.respond_to?(name = "__to_method_#{n}")
end
recv ||= eval("self", self)
klass = recv.class
MethodMutexes[klass => name].synchronize do
begin
klass.send(:define_method, name, &self)
return recv.method(name)
ensure
klass.send(:remove_method, name) unless name
end
end
end
end
> This can create a lot of different symbols, leading to large memory leaks.
>
> > recv ||= eval("self", self)
> > klass = recv.class
> > MethodMutexes[klass => name].synchronize do
> ============================
>
> This makes the memleak even heavier, going from potentially ~70 bytes to
> nearly 300 bytes per call...
I don't understand this though. isn't the memory freed when done? Or is
some symbol getting created in the process, or ?
> > begin
> > klass.send(:define_method, name, &self)
> ==========
>
> This will fail if the class was frozen.
Ah, I see if the _klass_ is frozen. But that will fail regardless won't
it? You can;t even include a module (ef InstanceExecHelper) in a frozen
class. Right?
Thanks,
T.
on 10.07.2006 19:41
On Tue, Jul 11, 2006 at 01:21:47AM +0900, transfire@gmail.com wrote: > Okay, I think I've improved it some more: > [...] > > > recv ||= eval("self", self) > > > klass = recv.class > > > MethodMutexes[klass => name].synchronize do > > ============================ > > > > This makes the memleak even heavier, going from potentially ~70 bytes to > > nearly 300 bytes per call... > > I don't understand this though. isn't the memory freed when done? Or is > some symbol getting created in the process, or ? Two things: * you're using a klass => name hash as the key (instead of [klass, name], which would take less memory) * MethodMutexes grows monotonically since you don't delete the key => mutex associations when you're done, so you keep the keys _and_ the mutexes, which are pretty heavy In the first version, the name included #{object_id}, so many values were possible (as many as different Procs you call to_method on, modulo object_id recycling). Different symbols and entries in the MethodMutexes hash would accumulate. > > > begin > > > klass.send(:define_method, name, &self) > > ========== > > > > This will fail if the class was frozen. > > Ah, I see if the _klass_ is frozen. But that will fail regardless won't > it? You can;t even include a module (ef InstanceExecHelper) in a frozen > class. Right? Yes, that's why InstanceExecHelper must be included in Object (and you only have to do that once). Here's an improved (hastily written, not thoroughly tested) version: class Object module InstanceExecHelper; end include InstanceExecHelper end class Proc # Creates a local method based on a Proc. def to_method(name=nil, recv=nil) recv ||= eval("self", self) do_remove = false begin old, Thread.critical = Thread.critical, true unless name do_remove = true n = 0 n += 1 while recv.respond_to?(name = "__to_method_#{n}") end me = self InstanceExecHelper.module_eval{ define_method(name, &me) } return recv.method(name) ensure if do_remove InstanceExecHelper.module_eval{ remove_method(name) rescue nil } end Thread.critical = old end end end module Kernel def instance_exec(*args, &block) block.to_method(nil,self).call(*args) end end class Dummy def f :dummy_value end end Dummy.freeze require 'test/unit' class TestInstanceEvalWithArgs < Test::Unit::TestCase def test_instance_exec # Create a block that returns the value of an argument and a value # of a method call to +self+. block = lambda { |a| [a, f] } assert_equal [:arg_value, :dummy_value], Dummy.new.instance_exec(:arg_value, &block) end def test_instance_exec_with_frozen_obj block = lambda { |a| [a, f] } obj = Dummy.new obj.freeze assert_equal [:arg_value, :dummy_value], obj.instance_exec(:arg_value, &block) end def test_instance_exec_nested i = 0 obj = Dummy.new block = lambda do |arg| [arg] + instance_exec(1){|a| [f, a] } end # the following assertion expanded by the xmp filter automagically from: # obj.instance_exec(:arg_value, &block) #=> assert_equal([:arg_value, :dummy_value, 1], obj.instance_exec(:arg_value, &block)) end end # >> Loaded suite - # >> Started # >> ... # >> Finished in 0.00091 seconds. # >> # >> 3 tests, 3 assertions, 0 failures, 0 errors
on 10.07.2006 22:16
> > I don't understand this though. isn't the memory freed when > > done? Or is some symbol getting created in the process, or > > ? define_method creates a symbol, beneath the surface: symbols1 = Symbol.all_symbols Module.new.instance_eval{define_method("bar"){}} symbols2 = Symbol.all_symbols p symbols2-symbols1 # ===> [:bar] > Yes, that's why InstanceExecHelper must be included in Object > (and you only have to do that once). You should include the Module before Object gets frozen. Who wants to freeze Object, anyway?... ;] > Thread.critical = old AFAIK, we need to be in critical mode when determining the method name. As soon as we know it, we can (and should...) return to old_critical. At least before we call the block/method. > module Kernel > def instance_exec(*args, &block) Kernel#instance_exec ? Does that make sense? About $SAFE: It's possible to do an instance_eval up to level 5, whereas instance_exec fails on level 4. Challenge?... ;] gegroet, Erik V. - http://www.erikveen.dds.nl/
on 10.07.2006 22:39
Mauricio Fernandez wrote:
> Here's an improved (hastily written, not thoroughly tested) version:
Okay, I took your draft and worked on it some more and came up with the
following, but there's something very strange....
require 'thread'
class Object
module TemporaryMethods; end
include TemporaryMethods
end
class Proc
# Creates a local method based on a Proc.
def to_method(name=nil, recv=nil)
recv ||= eval("self", self)
klass = name ? recv.class : TemporaryMethods
begin
old, Thread.critical = Thread.critical, true
unless name
n = 0
n += 1 while klass.method_defined?(name = "__to_method_#{n}")
#n += 1 while recv.respond_to?(name = "__to_method_#{n}")
end
me = self
klass.module_eval{ define_method(name, &me) }
recv.method(name) #if klass == TemporaryMethods
ensure
if klass == TemporaryMethods
klass.module_eval{ remove_method(name) } #rescue nil }
#klass.module_eval{ undef_method(name) } #rescue nil }
end
Thread.critical = old
end
end
end
Notice the two remarks on n+=1... and klass.module_eval...., if you
unremark the first one, the one with recv.respond_to? it doesn't alwasy
see that the method has been removed -- I have to idea why that is
since it certainly has been. If you use undef_method instead of
remove_method, it work again though! That really blows my min b/c I
though undef_method didn;t actually remove the method. So I'm really
confused about that. Here's the test I use for the Symbols:
# This is dependent on other tests! Be aware!
def test_memory
a = 2
tproc = proc { |x| x + a }
100.times {
tmethod = tproc.to_method
assert_equal( 3, tmethod.call(1) )
}
meths = Symbol.all_symbols.select { |s| s.to_s =~ /^__to_method/
}
assert_equal( 1, meths.size )
end
Also is the 'resuce nil' really needed?
T.
P.S. Is there any reason you use module_eval { define_method... rather
then send(:defined_method?...
on 10.07.2006 23:02
> end > > Also is the 'resuce nil' really needed? You can't remove a method if it isn't defined. If it's the define_method which fails, you won't see the root cause. Compare the scripts below. test1.rb doesn't give you a clue about the real problem. test2.rb does. It's probably better to give a real warning in this rescue block, instead of returning just nil. gegroet, Erik V. - http://www.erikveen.dds.nl/ ---------------------------------------------------------------- $ cat test1.rb begin Object.define_method(:bar){} ensure Object.module_eval{ remove_method(:bar) } end $ cat test2.rb begin Object.define_method(:bar){} ensure Object.module_eval{ remove_method(:bar) } rescue nil end $ ruby test1.rb test1.rb:4:in `remove_method': method `bar' not defined in Object (NameError) from test1.rb:4 from test1.rb:4 $ ruby test2.rb test2.rb:2: private method `define_method' called for Object:Class (NoMethodError)