My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
Yesterday, Ola Bini (from the JRuby team) described a new feature in JRuby that lets you steal methods from one class and add them to an object of another class. Ordinarily, you could only reuse methods through modules (as mixins), or of course the usual inheritance or aggregation. Police Line: Do Not Cross

It turns out someone wrote evil.rb, which does that and more in Ruby. Somehow, they are messing with Ruby's internals, or so I've read. I browsed the source quickly, and to be honest I don't have the experience to understand the trickery well enough to do a sam_larbi("evil.rb").inspect. My guess is that it will not work outside of MRI.

When Ola said the magic words - "there is no way to get hold of a method and add that to an unrelated other class or module" - I had to give it my own shot, however.

#first, we create our thief
module Thief
  def steal_method(klass, meth)
    k = klass.new
    self.class.send(:define_method, meth, k.method(meth).to_proc)
  end
end
#then, add him to Object
class Object
  include Thief
end

class A
  def initialize
    @which_class = "A"
  end
  def foo
    puts "A#foo called"
  end
  def bar(arg)
    puts "A#bar called with '" + arg + "'"
  end
end

class B
  def initialize
    @which_class = "B"
  end
end

b = B.new

b.steal_method A, :foo
b.foo

b.steal_method A, :bar
b.bar("I'm here!")

puts "Everything looks to be working as we expected!"

#change foo to see which member variable it's using
class A
  def foo
    puts "A#foo called from class " + @which_class
  end
end

#need to steal it again because it changed since we instantiated A
b.steal_method A, :foo
b.foo #outputs "A#bar called from class A"
#oh no!  we stole the method but it came with A's @which_class

So it turns out if we just used simple examples where the methods are self-contained, it appears to work, but when those methods need to use object-specific data, it's not going to do what we want it to. Now, there may be a way to fake it by going through A and adding everything we need from B, but using this plan at least, even that won't get us what we really want.

Any ideas on other approaches?

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

And in CF this is as easy as:

objA.stolenMethod = objB.someMethod;

Or, if the method is private:

function steal() { return variables; }

objB.thief = steal;

objA.stolenMethod = objB.thief().someMethod;

Posted by Sean Corfield on Jul 12, 2007 at 04:13 PM UTC - 6 hrs

Thanks Sean! I thought the same thing on my way across town, and had meant to update the post, but got sidetracked and forgot about it.

I haven't seen the JRuby implementation, nor did I dig deep enough into the evil.rb one, so I wonder if they actually instantiate an object of class A to steal it or not. I think that would be the only potential difference.

Of course, my own failed attempt in Ruby tried to instantiate an object of class A as well.

On the other hand, I suppose in CF we can get the method by cfincluding the CFC. We'd get all the methods that way, but if we encapsulated it we could remove the ones whose name didn't match the one we asked for. I haven't tried it to see what happens when method names conflict, though I imagine it'd throw something like "method is already defined" exception.

Posted by Sam on Jul 12, 2007 at 04:46 PM UTC - 6 hrs

Oops... When I said:

"I suppose in CF we can get the method by cfincluding the CFC."

I meant:

I suppose in CF we can get the method *without instantiation* by cfincluding the CFC."

Haven't tried that in the latest version though...

Posted by Sam on Jul 12, 2007 at 04:47 PM UTC - 6 hrs

Including a CFC effectively instantiates it - CFCs are compiled to pages anyway so including it will run the pseudo-constructor.

Posted by Sean Corfield on Jul 12, 2007 at 04:53 PM UTC - 6 hrs

Never thought about that ... =)

Posted by Sam on Jul 12, 2007 at 05:03 PM UTC - 6 hrs

you could also just get the method reference (as a proc)
and then pass it out to store it in any kind of variable, even in the main object :-P

module Thief
def get_method_ref(klass,meth)
k = klass.new
k.method(meth).to_proc
end
end

h = {}
h[ :my_foo ] = get_method_ref(A, :foo)
=> A#foo called from class A

# really bad idea.. but fun :) effectively a pointer to a method + binding

Posted by Tilo on May 27, 2011 at 09:34 PM UTC - 6 hrs

Hey Tilo,

Any ideas on how to get B's stolen Foo to reference B's instance variable instead of A's?

Posted by Sammy Larbi on Jun 12, 2011 at 12:38 PM UTC - 6 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and C++ (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner