Singleton methods without the singleton class

  • Thread starter Charles Oliver Nutter
  • Start date
C

Charles Oliver Nutter

Hi all!

JRuby currently allows you to add singleton methods to normal Java
objects like this:

foo = java.lang.String.new('blah')
def foo.bar
# do something
end

Unfortunately, this feature requires us to keep a weak table of all
the Java objects that enter Ruby space, since we can't attache this
singleton method directly to the object itself. I would like to
eliminate this feature at some point, but I recognize that since
people are using it we need an alternative.

So I've hacked together a short script that provides a "Singletonizer"
module that can simulate singleton methods without actually creating a
new singleton class:

http://gist.github.com/333174

The basic idea is to go ahead and add the method to the actual class,
but add it via a hashed lookup on a per-instance basis. Objects which
have had the method added will have a corresponding entry in their
attached_methods table. Objects that don't will raise NoMethodError as
usual.

Thoughts? Comments? This version is obviously not threadsafe, but it
gets you pretty close to singleton methods without requiring singleton
classes.

- Charlie
 
B

Brian Candler

Charles said:
Thoughts? Comments? This version is obviously not threadsafe, but it
gets you pretty close to singleton methods without requiring singleton
classes.

What if you are defining a singleton method to override a method which
already exists in the class? It's fixable...

module Singletonizer
def attach_method(name, &block)
(@attached_methods ||= {})[name] = block
class_method = "__class_#{name}"
return if respond_to? class_method
if respond_to? name
self.class.class_eval "alias :#{class_method} :#{name}"
else
self.class.class_eval <<-RUBY
def #{class_method}(*args)
ex = NoMethodError.new("undefined method `#{name}' for
\#{self.inspect}:\#{self.class}")
ex.set_backtrace caller(2)
raise ex
end
RUBY
end
self.class.class_eval <<-RUBY, __FILE__, __LINE__
def #{name}(*args)
if (defined? @attached_methods) && (block =
@attached_methods[:#{name}])
instance_exec(*args, &block)
else
__class_#{name}(*args)
end
end
RUBY
end
end

...unless someone comes along and decides to change the definition in
the class :-(

I note that using the block syntax for defining singleton methods means
that singleton methods can't take a block. But that's not an problem if
this is just a demo of something which will take place under the hood.
 
I

Intransition

JRuby currently allows you to add singleton methods to normal Java
objects like this:

foo =3D java.lang.String.new('blah')
def foo.bar
=A0 # do something
end

Unfortunately, this feature requires us to keep a weak table of all
the Java objects that enter Ruby space, since we can't attache this
singleton method directly to the object itself. I would like to
eliminate this feature at some point, but I recognize that since
people are using it we need an alternative.

So I've hacked together a short script that provides a "Singletonizer"
module that can simulate singleton methods without actually creating a
new singleton class:

http://gist.github.com/333174

The basic idea is to go ahead and add the method to the actual class,
but add it via a hashed lookup on a per-instance basis. Objects which
have had the method added will have a corresponding entry in their
attached_methods table. Objects that don't will raise NoMethodError as
usual.

Thoughts? Comments? This version is obviously not threadsafe, but it
gets you pretty close to singleton methods without requiring singleton
classes.

How do you handle #extend?

I've always thought it would be wise if singleton methods were added
to an anonymous module rather then directly to the object.* Perhaps
taking this approach will work for JRuby. While not exactly like MRI
it should be close enough for all practical purposes.

(* Which is why Facets extends the #extend method to do exactly that
if you supply it a block.)
 
C

Charles Oliver Nutter

What if you are defining a singleton method to override a method which
already exists in the class? It's fixable...

The problem with this is that it then makes all callers to all
instances of that class go through all this logic. Maybe that's not a
big deal?

I've thrown this stuff into a github project here (without your
changes for the moment):

http://github.com/headius/singletonizer

I also added a mutex around the whole body of attach_method and
renamed attach_method to "def", so the singletonizing looks a bit more
like normal singleton methods:

instead of

def obj.foo
blah
end

you do

obj.def :foo do
blah
end
I note that using the block syntax for defining singleton methods means
that singleton methods can't take a block. But that's not an problem if
this is just a demo of something which will take place under the hood.

Yeah, I don't know of a way to make that work using just Ruby
features; it would be possible to do it under the covers in JRuby,
though.

- Charlie
 
C

Charles Oliver Nutter

How do you handle #extend?

I don't :)

#extend would be hard to support with this, since module methods are
often only callable against the object themselves; in other words, if
the module doesn't *actually* get inserted into the object's class
hierarchy, the methods won't be invokable.

Again, this can be forced under the covers in JRuby, but not through
any normal Ruby mechanisms I know of.
I've always thought it would be wise if singleton methods were added
to an anonymous module rather then directly to the object.* Perhaps
taking this approach will work for JRuby. While not exactly like MRI
it should be close enough for all practical purposes.

(* Which is why Facets extends the #extend method to do exactly that
if you supply it a block.)

Yes, that's probably wise, but #extend is mostly a deal-breaker in any
form because it forces a singleton object to be created.

- Charlie
 

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

Forum statistics

Threads
473,968
Messages
2,570,152
Members
46,698
Latest member
LydiaHalle

Latest Threads

Top