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.
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!
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