ruby picture

RCR 292: (Unbound)Method#origin and #name and Method#target

Submitted by flgr (Mon Feb 14 21:02:06 UTC 2005)

Abstract

It's sometimes necessary to go back from a (Unbound)Method object to the place (Module, Class or singleton class) where it and its name when it was defined. This would allow that.

Problem

Refering to the origin and name of a (Unbound)Method can for example be important when you want to add meta information to those objects that is queried from the Class/Module where it was defined.

I can not think of an actual sample of what Method#target could be used for right now, but I think it would not hurt to add it either. It's the one that could be removed from this RCR most easily.

Proposal

Add (Unbound)Method#origin and #name and Method#target. #name might be renamed to #original_name if that makes more sense.

The effort to implement this ought to be fairly low as the structs already contain the information anyway. (UnboundMethod#inspect uses it.)

#origin would return the Class or Module where the (Unbound)Method was originally defined. #name would return the original method name as either a String or Symbol. (I prefer Symbol) #target would return the bind target -- the Object the Method is bound to.

Analysis

Currently method.name could be emulated by doing method.inspect[/\A.+[.#](.+?)\Z/, 1], but that is quite wordy.

It's not so easy with method.origin -- it can be done in case the origin was not an anonymous Module or Class, but it requires a bit more Regexp work and using the module_name.split("::").inject(Object) { |mod, item| mod.const_get(item) } trick.

Method#target can not be implemented at all in pure Ruby as far as I know.

In the proposal I mentioned possibly renaming #name to #original_name -- this is because the #name might already be reused for another method at the time the call is made or not be used at all. See this code sample for when this would happen:


    
  class Foo
    def x() 1 end
  end
  old_x = Foo.instance_method(:x)
  class Foo
    remove_method :x
    # old_x.name still returns :x, but old_x.origin.instance_method(old_x.name) raises an Exception
    def x() 2 end
    # old_x.name still returns :x, but old_x.origin.instance_method(old_x.name).bind(obj).call != old_x.bind(obj).call
  end

    

I don't think that the behavior is much of a problem when pointed out in the documentation, but I thought pointing it out would be a good idea.

Implementation

I implemented bits of it in Ruby already in the Analysis, but I think a complete Ruby implementation is only possible with EvilRuby style tricks.
ruby picture
Comments Current voting
Perhaps #__name__, to correspond to #__id__ and #__send__ as "core" names? -- AustinZ.


The Object#__id__ and Object#__send__ methods are named that way because of the enourmously dense namespace they inhabit. For instance, an object representing an XML element might want to define an #id method, and an object representing a network socket might want to define #send. I see no point in using the name Method#__name__, since we don't expect people to subclass Method and define a #name method of their own. It would make about as much sense as Module#__define_method__, Proc#__call__, or even String#__split__.


Could someone explain these better. Primarily what is #target?


#target simply returns the object the method is bound to. 5.method(:+).target would return 5.


Yes, so if we: a = Klass.new b = a.method: name b.target => would return the object that 'a' points to. b.origin => would return Klass.

++ On this one! I logged into RCR to suggest the exact same thing in fact :-p.


I've implemented these methods in Nodewrap. Here's the relevant C implementation. Method#target corresponds to method_receiver, Method#name corresponds to either method_id or method_oid (depending on whether you want the current or original name), and Method#origin corresponds to method_origin_class:

  /*
   * Given a Method, returns the Object to which it is bound.
   */
  static VALUE method_receiver(VALUE method)
  {
    struct METHOD * m;
    Data_Get_Struct(method, struct METHOD, m);
    return m->recv;
  }
  /*
   * Given a Method, returns the Symbol of the method it represents.  If
   * the method is an alias for another method, returns the Symbol of the
   * new method, not the original.  If the method changes name, returns
   * the original name, not the new name.
   */
  static VALUE method_id(VALUE method)
  {
    struct METHOD * m;
    Data_Get_Struct(method, struct METHOD, m);
    return ID2SYM(m->id);
  }
  /*
   * Given a Method, returns the Symbol of the method it represents.  If
   * the method is an alias for another method, returns the Symbol of the
   * original method, not the alias.  If the original method changes name,
   * returns the original name.
   */
  static VALUE method_oid(VALUE method)
  {
    struct METHOD * m;
    Data_Get_Struct(method, struct METHOD, m);
    return ID2SYM(m->oid);
  }
  /*
   * Given a Method, returns the Class in which the method it represents
   * was defined.  If the method was defined in a base class and
   * Object#method is called on a derived instance of that base class,
   * this method returns the base class.
   */
  static VALUE method_origin_class(VALUE method)
  {
    struct METHOD * m;
    Data_Get_Struct(method, struct METHOD, m);
  #if RUBY_VERSION_CODE < 180
    return m->oklass;
  #else
    return m->rklass;
  #endif
  }

-- Paul Brannan


Strongly opposed 0
Opposed 0
Neutral 0
In favor 8
Strongly advocate 1
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .