Feature #12084
closed`Class#instance`
Description
For meta-programming/debugging purposes, I would like to request the inverse of Object#singleton_class
. Namely, a method that is called on a class that is a singleton class, and returns the object it is a singleton of. Since the Singleton
module in the standard library http://ruby-doc.org/stdlib-2.3.0/libdoc/singleton/rdoc/Singleton.html assigns the method name instance
to such classes, I think Class#instance
should be the name for such feature.
Array.singleton_class.instance # => Array
"foo".singleton_class.instance # => "foo"
When the receiver is a class but is not a singleton class, then it should raise an error.
Array.instance # => error
Updated by justcolin (Colin Fulton) almost 9 years ago
This feature would solve a lot of problems I had while doing what should have been simple meta-programming (if there is such a thing as "simple" meta-programming...).
Something to consider is what happens with nested singleton classes. Let's say you have this code:
Array.singleton_class
.singleton_class
.instance
Does that return Array
or the first singleton class? I would think the former is more useful, but less obvious.
Updated by jeremyevans0 (Jeremy Evans) about 3 years ago
- Related to Feature #12655: Accessing the method visibility added
Updated by Eregon (Benoit Daloze) about 3 years ago
#instance
seems a too generic name for such a rarely-needed meta-programming feature, I think #singleton_instance
is better.
@justcolin (Colin Fulton) it would result in Array.singleton_class
, otherwise that would break the invariant of #singleton_instance
being the reverse of #singleton_class
.
A concrete use-case would be welcome.
Updated by ufuk (Ufuk Kayserilioglu) about 3 years ago
Agreed that instance
is a bad name for this concept and we should not be basing the name on the Singleton
class, since it is not the same kind of singleton in the singleton_class
.
From what I understand this would be exposing the concept of "attached_object" to user code and that concept is clearly documented inside the CRuby source code to mean exactly this:
...
* - attached object: A singleton class knows its unique instance.
* The instance is called the attached object for the singleton class.
...
So a good name would be attached_object
and its behaviour could be:
Array.singleton_class.attached_object # => Array
"foo".singleton_class.attached_object # => "foo"
Array.attached_object # => nil
"foo".attached_object # => nil
Updated by sawa (Tsuyoshi Sawada) about 3 years ago
ufuk (Ufuk Kayserilioglu) wrote in #note-4:
Array.attached_object # => nil "foo".attached_object # => nil
That clearly cannot be accepted as a feature because you would then have no way to distinguish whether the class's singleton object is nil
or it does not have a singleton object. Note the following:
NilClass.instance # => nil
Updated by matz (Yukihiro Matsumoto) about 3 years ago
-
instance
is NG. For example,Array.instance => nil
is confusing -
attached_object
is better, at least for singleton classes. But there's still no real-world use-case.
For your information, NilClass
is not a singleton class. It's a class with only an instance. This is side evidence of this method is confusing. Even the original proposer can misunderstand the concept.
Matz.
Updated by Eregon (Benoit Daloze) about 3 years ago
For your information,
NilClass
is not a singleton class. It's a class with only an instance. This is side evidence of this method is confusing. Even the original proposer can misunderstand the concept.
Interesting, I think what confuses people (including myself) is nil.singleton_class # => NilClass
.
And basically that's because #singleton_class
has special behavior for true/false/nil (special_singleton_class_of
).
Such behavior is not used for any other Ruby value (the rest is TypeError or create a new anonymous singleton class).
It's a bit funny too:
irb(main):002:0> TrueClass.singleton_class?
=> false
irb(main):003:0> true.singleton_class.singleton_class?
=> false
It kind of makes sense given TrueClass
, etc are defined as regular classes, and then creating an extra/separate singleton class would be confusing (methods could be defined on either).
I guess it would be possible to create TrueClass as a singleton class (with superclass Object
) and name it, but that's another proposal and the pros/cons are unclear to me at this stage.
Updated by jemmai (Jemma Issroff) about 3 years ago
matz (Yukihiro Matsumoto) wrote in #note-6:
But there's still no real-world use-case.
We have defined the method described here , for MemoWise, a memoization gem. (For what it’s worth, we named it original_class_from_singleton
.)
It’s feasible that someone wants to memoize a method on a singleton class. Here is an example*:
require "memo_wise"
class << String
prepend MemoWise
def example_method
"example"
end
memo_wise :example_method
end
String.example_method
# => “example”
Within MemoWise, we receive the memo_wise(:example_method)
call on the singleton class of String
, and must resolve it back to define the memoization on the String
class itself, not its singleton class. We therefore resolve the original class by searching ObjectSpace
for the class whose singleton class is the one we received:
def self.original_class_from_singleton(klass)
ObjectSpace.each_object(Module).find do |cls|
cls.singleton_class == klass
end
end
I believe this is almost exactly the built in method being proposed.
*It is worth noting that this same functionality could be achieved by the following snippet, instead of opening up the singleton class:
require "memo_wise"
class String
prepend MemoWise
def self.example_method
"example"
end
memo_wise self: :example_method
end
String.example_method
# => "example"
But I think part of the beauty of Ruby is that there are multiple ways to express the same sentiment, and while we allow for opening up the singleton class to do this, it makes sense to me that a gem like MemoWise must support this case, and therefore a method as defined in this issue does have a real world use case. It’s also worth noting that a different design decision in MemoWise might make the need go away soon for MemoWise specifically.
Updated by Eregon (Benoit Daloze) about 3 years ago
jemmai (Jemma Issroff) wrote in #note-8:
Within MemoWise, we receive the
memo_wise(:example_method)
call on the singleton class ofString
, and must resolve it back to define the memoization on theString
class itself, not its singleton class.
That's not clear to me, why do you need to access String? Can't the memoization be stored on the singleton class (String.singleton_class)?
Modules/classes are always "unique", there are no two same instances, so it seems always fine to store the data on the class/module holding/owning (Method#owner
) the method (the module/class for which method_defined?(name)
is true/has it in instance_methods
).
# * Performance concern: searches all Class objects # But, only runs at load time and results are memoized
BTW, it (internally) iterates all objects on most Ruby implementations, not just modules/classes, the filtering is typically done on top because there is no "next instance pointer" (e.g., unlike in some Smalltalk).
ObjectSpace.each_object
is a clear no-no when anyone cares about the time to load a library/use it during startup (it's O(nb of objects in heap at the time of call)
).
Updated by Eregon (Benoit Daloze) almost 3 years ago
- Related to Bug #11063: Special singleton class should return true for singleton_class? test added
Updated by ufuk (Ufuk Kayserilioglu) over 2 years ago
matz (Yukihiro Matsumoto) wrote in #note-6:
attached_object
is better, at least for singleton classes. But there's still no real-world use-case.
I recently ran into a real-world use-case inside one of the gems I maintain and I would like make another case for this request.
Use Case - Introspection¶
To give some context, Tapioca is a gem to generate RBI files for gems and for other DSL generated runtime methods. Tapioca needs to rely heavily on runtime reflection and introspection to achieve what it does so that it can generate a proper RBI representation of what a gem intended to export from its implementation.
One feature we recently added was a way to attribute modules in the ancestor chain of a module/class to the correct gem that the module was mixed in from. In order to do this, we need to hook into the include/extend/prepend operations to keep track of where the mixins are coming from. This part is all working fine, until we get a mixin on the singleton class of a class like:
module Foo
end
class Bar
class << self
include Foo
end
end
In this case, the mixin is happening on #<Class:Bar>
but Tapioca needs to match that to one of the ancestors of Bar
so that it can generate the mixin in the RBI file. In order to do that, Tapioca needs to be able to find Bar
from #<Class:Bar>
.
Workarounds¶
Our first (naive and failed) attempt to do that was to parse the to_s
representation of the singleton class to figure out the Bar
part. This failed, because some classes override the inspect
method on the class. Most prominently ActiveRecord::Base
subclasses have an overridden inspect
class method that has modified output. This ends up changing the output of the inspect
method on the singleton class, since Ruby makes an internal call to inspect
on the attached class to get the class name. It is impossible to get the original inspect
method called via bind_call
tricks, since the call to the attached class inspect
method happens inside Ruby.
Reluctantly, we had to resort to the ObjectSpace
walk solution for matching a singleton class to its attached object (which is a class in our case), which really slowed down our implementation.
Relevant work on Tapioca can be found here and here
Suggestion¶
However uncommon, this use-case does not currently have a good workaround, other than resorting to the terrible method of walking the object space for each identification we need to perform.
If this is a compelling use-case for Class#attached_object
feature, I would love to implement it such that a call to Class#attached_object
throws if the receiver is not a singleton class to begin with. I think that has the cleanest semantics, since callers can always check with Module#singleton_class?
before calling Class#attached_object
.
So the proposed implementation would work like this:
# Success cases
Array.singleton_class.attached_object # => Array
Array.singleton_class.singleton_class.attached_object # => #<Class:Array>
"foo".singleton_class.attached_object # => "foo"
# Error cases
Array.attached_object # => error
"foo".attached_object # => error
NilClass.attached_object # => error (since `NilClass.singleton_class? #=> false`)
true.singleton_class.attached_object # => error (since `true.singleton_class.singleton_class? #=> false`)
Updated by Dan0042 (Daniel DeLorme) about 2 years ago
I think it would be nicer if #attached_object
returns nil instead of raising an error. Since nil can't be a valid return value anyway. So you don't need to check for #singleton_class?
if you want to avoid the cost of Exceptions.
Find all singleton objects:
ObjectSpace.each_object(Class).filter_map{ |c| c.attached_object rescue nil }
vs
ObjectSpace.each_object(Class).filter_map{ |c| c.attached_object if c.singleton_class? }
vs
ObjectSpace.each_object(Class).filter_map(&:attached_object)
Updated by ufuk (Ufuk Kayserilioglu) about 2 years ago
Dan0042 (Daniel DeLorme) wrote in #note-12:
I think it would be nicer if
#attached_object
returns nil instead of raising an error. Since nil can't be a valid return value anyway. So you don't need to check for#singleton_class?
if you want to avoid the cost of Exceptions.
I have no problems with returning nil
if that is desired, since, as you said, nil
is never going to be a valid return value. However, I fear that people will see:
NilClass.attached_object # => nil
nil.singleton_class # => NilClass
and expect NilClass.singleton_class?
to be true
, but it is false
. For that reason, raising an error felt safer.
Additionally, as I stated above, this is not meant to be an easy to use API for mass consumption, so terseness of the code that uses this method is not my primary concern. As in most reflection related APIs (looking at const_source_location
and autoload?
: https://bugs.ruby-lang.org/issues/17354) the usage can be complicated as long as it gets the job done.
@matz (Yukihiro Matsumoto) Do you have any opinions on the final state of this feature request?
Updated by ufuk (Ufuk Kayserilioglu) about 2 years ago
I put together an implementation for this and created a PR: https://github.com/ruby/ruby/pull/6450
Updated by Eregon (Benoit Daloze) about 2 years ago
I'm +1 for adding Class#attached_object
.
nil
vs raise
seem both fine to me, although I slightly prefer raise
for clarity for the nil
case.
Updated by matz (Yukihiro Matsumoto) about 2 years ago
attached_object
looks good to me. Accepted.
Matz.
Updated by ufuk (Ufuk Kayserilioglu) about 2 years ago
- Status changed from Open to Closed
Applied in changeset git|0378e2f4a8319440dd65c82b16f189161472d237.
Add Class#attached_object
Implements [Feature #12084]
Returns the object for which the receiver is the singleton class, or
raises TypeError if the receiver is not a singleton class.