get object from its object-specific class?

T

Thomas Hafner

Hello,

is there a way to get an object, if only its object-specific class is
given? For instance, given

c = class << Hash[:a, 5]
def mult(x)
self[:a] * x
end
self
end

can I retrieve the hash object via evaluation of c?

Regards
Thomas
 
A

ara.t.howard

Hello,

is there a way to get an object, if only its object-specific class is
given? For instance, given

c = class << Hash[:a, 5]
def mult(x)
self[:a] * x
end
self
end

can I retrieve the hash object via evaluation of c?

harp:~ > cat a.rb
c = class << Hash[:a, 5]
def mult(x)
self[:a] * x
end
self
end

p c.instance_eval{ self }


harp:~ > ruby a.rb
#<Class:#<Hash:0xb75cfd18>>

if you are doing much of this sort of thing check out the prototype lib


harp:~ > cat a.rb
require 'rubygems'
require 'prototype'

c = Object.prototype(Hash) do
def mult(x)
self[:a] * x
end

def initialize
self[:a] = 5
end
end

p c



harp:~ > ruby a.rb
{:a=>5}



-a
 
T

Trans

is there a way to get an object, if only its object-specific class is
given? For instance, given
c = class << Hash[:a, 5]
def mult(x)
self[:a] * x
end
self
end
can I retrieve the hash object via evaluation of c?

harp:~ > cat a.rb
c = class << Hash[:a, 5]
def mult(x)
self[:a] * x
end
self
end

p c.instance_eval{ self }

harp:~ > ruby a.rb
#<Class:#<Hash:0xb75cfd18>>

I thought we wanted the result to be #<Hash:0xb75cfd18>?

T.
 
P

Pit Capitain

Trans said:
I thought we wanted the result to be #<Hash:0xb75cfd18>?

Tom, I thought so, too, but I didn't want to show my solution before
there's a demand for it :)

Regards,
Pit
 
R

Rick DeNatale

hought we wanted the result to be #<Hash:0xb75cfd18>?

rick@frodo:/public/rubyscripts$ ruby object_from_sc.rb
#<Class:#<Hash:0xb7df1774>>
{:a=>5}
oXb7df1774

And here's how:

rick@frodo:/public/rubyscripts$ cat object_from_sc.rb
# this method returns an integer value of the address of an object
with a standard
# object id.
# Note that internally Ruby FixedNums are stored as a value which is
the integer value
# shifted left one bit with a 1 bit ored into the LSB.
# For most objects the object_id is just the address with the LSB set.
This method
# undoes this magic to reveal the actual address.
def hex_oid(obj)
"oX#{(obj.object_id*2 & 0xffffffff).to_s(16)}"
end

c2 = class << {:a => 5}
def mult(x)
self[:a] * x
end
self
end

p c2

ObjectSpace.each_object(c2) {|o| p o; puts hex_oid(o)}
 
R

Rick DeNatale

By the way.

I was a little concerned that having just a reference to a singleton
class without a reference to its single instance might not protect
that instance from being GCed.

I took a quick look through the ruby1.8 source and it looks like
singleton class objects have a hidden instance variable named
__attached__ which refers to the instance. This is what it uses to
compute it's string representation for example.

I don't think that theres any way to get at that through Ruby however,
for example instance_variable_get:)"__attached__") complains that
__attached__ is not a legal instance variable name.

So I guess you could write a C extension which accessed that hidden
variable, but in pure Ruby scanning using ObjectSpace.each_object
works albeit potentially less efficiently.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

IPMS/USA Region 12 Coordinator
http://ipmsr12.denhaven2.com/

Visit the Project Mercury Wiki Site
http://www.mercuryspacecraft.com/
 
P

Pit Capitain

Rick said:
ObjectSpace.each_object(c2) {|o| p o}

Yeah Rick, this is what I came up with at first, too. But I didn't like
that it examines each Ruby object. Here's another solution, much
uglier but also faster:

def instance_of_singleton_class_1(sc)
ObjectSpace.each_object(sc) do |obj|
return obj
end
end

def instance_of_singleton_class_2(sc)
obj = nil
sc.class_eval do
if instance_methods(false).include?("singleton_method_added")
org = instance_method("singleton_method_added")
remove_method("singleton_method_added")
end
define_method("singleton_method_added") do |m|
obj = self
end
remove_method("singleton_method_added")
define_method("singleton_method_added", org) if org
end
obj
end

Test that they both work the same:

sc = class << {:a => 5}; self; end

p instance_of_singleton_class_1(sc) # => {:a=>5}
p instance_of_singleton_class_2(sc) # => {:a=>5}

And the benchmark:

require "benchmark"

Benchmark.bmbm do |bm|
bm.report "ObjectSpace" do
1000.times do instance_of_singleton_class_1(sc) end
end
bm.report "singleton_method_added" do
1000.times do instance_of_singleton_class_2(sc) end
end
end

yields:

Rehearsal ----------------------------------------------------------
ObjectSpace 3.109000 0.000000 3.109000 ( 3.188000)
singleton_method_added 0.266000 0.000000 0.266000 ( 0.265000)
------------------------------------------------- total: 3.375000sec

user system total real
ObjectSpace 3.016000 0.000000 3.016000 ( 3.032000)
singleton_method_added 0.266000 0.000000 0.266000 ( 0.266000)

Regards,
Pit
 
T

Trans

Yeah Rick, this is what I came up with at first, too. But I didn't like
that it examines each Ruby object. Here's another solution, much
uglier but also faster:

def instance_of_singleton_class_1(sc)
ObjectSpace.each_object(sc) do |obj|
return obj
end
end

def instance_of_singleton_class_2(sc)
obj = nil
sc.class_eval do
if instance_methods(false).include?("singleton_method_added")
org = instance_method("singleton_method_added")
remove_method("singleton_method_added")
end
define_method("singleton_method_added") do |m|
obj = self
end
remove_method("singleton_method_added")
define_method("singleton_method_added", org) if org
end
obj
end

Test that they both work the same:

sc = class << {:a => 5}; self; end

p instance_of_singleton_class_1(sc) # => {:a=>5}
p instance_of_singleton_class_2(sc) # => {:a=>5}

And the benchmark:

require "benchmark"

Benchmark.bmbm do |bm|
bm.report "ObjectSpace" do
1000.times do instance_of_singleton_class_1(sc) end
end
bm.report "singleton_method_added" do
1000.times do instance_of_singleton_class_2(sc) end
end
end

yields:

Rehearsal ----------------------------------------------------------
ObjectSpace 3.109000 0.000000 3.109000 ( 3.188000)
singleton_method_added 0.266000 0.000000 0.266000 ( 0.265000)
------------------------------------------------- total: 3.375000sec

user system total real
ObjectSpace 3.016000 0.000000 3.016000 ( 3.032000)
singleton_method_added 0.266000 0.000000 0.266000 ( 0.266000)


I imagine ObjectSpace._id2ref and parsing inspect would work.

T.
 
P

Pit Capitain

Trans said:
I imagine ObjectSpace._id2ref and parsing inspect would work.

Too easy, Tom, much too easy :) Yes, this should work, too, of course.
The only gotchas are that you should make sure that the original #to_s
method is used, and you'd have to add a special case for singleton
classes of modules:

p class << Module; self; end # => #<Class:Module>

Regards,
Pit
 
P

Pit Capitain

Pit said:
The only gotchas are that you should make sure that the original #to_s
method is used, and you'd have to add a special case for singleton
classes of modules:

p class << Module; self; end # => #<Class:Module>

Tom, sorry for the noise, but you can create pathological situations
where you can't get at the module via its name:

obj = M = Module.new
obj.to_s

Object.class_eval do
remove_const "M"
end

p class << obj; self; end # => #<Class:M>
p Object.const_get("M") rescue p $! # => #<NameError: M>

Maybe someone doing this deserves some troubles...

Regards,
Pit
 
R

Rick DeNatale

Two approaches with a benchmark.

And as I surmised, there's a really quick way to do this with a
relatively simple extension (at least for ruby1.8, I haven't looked at
this on 1.9).

rick@frodo:/public/rubyscripts/getsinginst$ cat get_sing_inst.c
#include "ruby.h"

static VALUE singleton_instance(VALUE obj)
{
if (BUILTIN_TYPE(obj) == T_CLASS && FL_TEST(obj, FL_SINGLETON)) {
return rb_iv_get(obj, "__attached__");
}
rb_raise(rb_eTypeError, "not a singleton class");
}

void Init_get_sing_inst()
{
rb_define_method(rb_cClass, "singleton_instance", singleton_instance, 0);

}

rick@frodo:/public/rubyscripts/getsinginst$ cat gsi.rb
require 'get_sing_inst'

h = {:a => 1}
sc = class << h
def foo
end

self
end

p sc.singleton_instance
p sc.singleton_instance.object_id == h.object_id

def instance_of_singleton_class_1(sc)
ObjectSpace.each_object(sc) do |obj|
return obj
end
end

def instance_of_singleton_class_2(sc)
obj = nil
sc.class_eval do
if instance_methods(false).include?("singleton_method_added")
org = instance_method("singleton_method_added")
remove_method("singleton_method_added")
end
define_method("singleton_method_added") do |m|
obj = self
end
remove_method("singleton_method_added")
define_method("singleton_method_added", org) if org
end
obj
end

require "benchmark"

Benchmark.bmbm do |bm|
bm.report "ObjectSpace" do
10000.times do instance_of_singleton_class_1(sc) end
end
bm.report "singleton_method_added" do
10000.times do instance_of_singleton_class_2(sc) end
end
bm.report "primitive" do
10000.times do sc.singleton_instance end
end
end


And now for the benchmark results, note that I'm doing 10 times as
many interations as Trans did, 10000 vs. 1000, the primitive is on the
order of two orders of magnitude faster than either of the other
approaches.

rick@frodo:/public/rubyscripts/getsinginst$ ruby gsi.rb
{:a=>1}
true
Rehearsal ----------------------------------------------------------
ObjectSpace 3.270000 0.100000 3.370000 ( 3.469175)
singleton_method_added 4.450000 0.110000 4.560000 ( 4.580452)
primitive 0.040000 0.000000 0.040000 ( 0.046098)
------------------------------------------------- total: 7.970000sec

user system total real
ObjectSpace 2.300000 0.050000 2.350000 ( 2.352730)
singleton_method_added 4.430000 0.070000 4.500000 ( 4.510719)
primitive 0.040000 0.000000 0.040000 ( 0.037970)
 
T

Trans

Two approaches with a benchmark.

And as I surmised, there's a really quick way to do this with a
relatively simple extension (at least for ruby1.8, I haven't looked at
this on 1.9).

rick@frodo:/public/rubyscripts/getsinginst$ cat get_sing_inst.c
#include "ruby.h"

static VALUE singleton_instance(VALUE obj)
{
if (BUILTIN_TYPE(obj) == T_CLASS && FL_TEST(obj, FL_SINGLETON)) {
return rb_iv_get(obj, "__attached__");
}
rb_raise(rb_eTypeError, "not a singleton class");

}

void Init_get_sing_inst()
{
rb_define_method(rb_cClass, "singleton_instance", singleton_instance, 0);

}

rick@frodo:/public/rubyscripts/getsinginst$ cat gsi.rb
require 'get_sing_inst'

h = {:a => 1}
sc = class << h
def foo
end

self
end

p sc.singleton_instance
p sc.singleton_instance.object_id == h.object_id

def instance_of_singleton_class_1(sc)
ObjectSpace.each_object(sc) do |obj|
return obj
end
end

def instance_of_singleton_class_2(sc)
obj = nil
sc.class_eval do
if instance_methods(false).include?("singleton_method_added")
org = instance_method("singleton_method_added")
remove_method("singleton_method_added")
end
define_method("singleton_method_added") do |m|
obj = self
end
remove_method("singleton_method_added")
define_method("singleton_method_added", org) if org
end
obj
end

require "benchmark"

Benchmark.bmbm do |bm|
bm.report "ObjectSpace" do
10000.times do instance_of_singleton_class_1(sc) end
end
bm.report "singleton_method_added" do
10000.times do instance_of_singleton_class_2(sc) end
end
bm.report "primitive" do
10000.times do sc.singleton_instance end
end
end

And now for the benchmark results, note that I'm doing 10 times as
many interations as Trans did, 10000 vs. 1000, the primitive is on the
order of two orders of magnitude faster than either of the other
approaches.

Actually, you mean Pit. He did the benchmarks. This is definitely the
way to do it right though. I would submit this to ruby-core mailing
list and see how it takes over there.

T.
 
R

Rob Pitt

def instance_of_singleton_class_2(sc)

not only is this method faster but it works in a situation where the
ObjectSpace does not. Consider this rspec test pasted in from a personal
introspection lib. It will work with this method but not with the
ObjectSpace:

def singleton
class<<self;self;end
end

def singleton_ancestors
result = []
return result unless pointer = singleton_parent
result << self
begin
result << pointer
end while pointer = pointer.singleton_parent
result
end

it "should get singletons back in the correct order" do
parent = ""
a = parent.singleton
b = a.singleton
c = b.singleton
d = c.singleton
e = d.singleton
f = e.singleton
f.singleton_ancestors.should == [ f, e, d, c, b, a, parent ]
end
 
D

David A. Black

Hi --

def instance_of_singleton_class_2(sc)

not only is this method faster but it works in a situation where the
ObjectSpace does not. Consider this rspec test pasted in from a personal
introspection lib. It will work with this method but not with the
ObjectSpace:

def singleton
class<<self;self;end
end

def singleton_ancestors
result = []
return result unless pointer = singleton_parent

I think you forgot to define singleton_parent.


David
 
R

Rob Pitt

No, this was deliberate. Maybe it was a mistake... My singleton_parent
retrieves __attached__, but I was saying Pit's
instance_of_singleton_class_2 method works as a replacement for this
where as ObjectSpace does not. Here, I've refactored the
instance_of_singleton_class_2 into a singleton_parent method for you:

def singleton_parent
# __instance_variable_get__( '__attached__' )
return nil unless is_a? Class
obj = nil
class_eval do
if instance_methods( false ).include?( "singleton_method_added" )
org = instance_method( :singleton_method_added )
remove_method( :singleton_method_added )
end
define_method( :singleton_method_added ) do |m|
obj = self
end
remove_method( :singleton_method_added )
define_method( :singleton_method_added, org) if org
end
obj
end
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top