Injecting methods from one class into another.

G

George Moschovitis

Hello everyone,

I am wondering if the following is possible in Ruby:

class Source
def my_method
puts 'IT WORKS'
end
end

class Dest
# no method
end

inject_method(Source, :my_method, Dest)

d = Dest.new
d.my_method # => IT WORKS

Is it possible to write 'inject_method' in Ruby ?
Thanks in advance,
George
 
T

ts

G> inject_method(Source, :my_method, Dest)

What do you expect with this ?

inject_method(Array, :[], Hash)


Guy Decoux
 
G

George Moschovitis

What do you expect with this ?
inject_method(Array, :[], Hash)

this is an artificial example. I expect Hash's [] to be replaced
with Array's version thus creating a bug.

however, I would like to use it carefully. My question was: is this
possible?

-g.
 
T

ts

G> however, I would like to use it carefully. My question was: is this
G> possible?

Well, you can use #instance_method and #bind but ruby will make the test

uln% ruby -e 'Array.instance_method:)[]).bind(Hash.new)'
-e:1:in `bind': bind argument must be an instance of Array (TypeError)
from -e:1
uln%



Guy Decoux
 
S

Szymon Drejewicz

Why you don't want to use module?

module CustomFunctionality
def my_method
puts 'IT WORKS'
end
end

class Source
include CustomFunctionality
end

class Dest
include CustomFunctionality

def another_method
puts 'HELLO'
end
end

d = Dest.new

d.my_method # => IT WORKS
 
G

George Moschovitis

Well, I know about modules, this is not what I want.

I want to inject only ONE method from a class containing multiple
methods into another.

-g.
 
G

Gennady Bystritksy

George said:
Well, I know about modules, this is not what I want.

I want to inject only ONE method from a class containing multiple
methods into another.

-g.
Object methods do not work in vacuum, they usually use instance
variables to reffer to the object's state, possibly changing it
(remember encapsulation ;-)?). How would you expect your
inject_method() deal with it?

Gennady.
 
G

George Moschovitis

Well, you can use #instance_method and #bind but ruby will make the
test
uln% ruby -e 'Array.instance_method:)[]).bind(Hash.new)'
-e:1:in `bind': bind argument must be an instance of Array (TypeError)
from -e:1

this test really kills 'duck typing', and renders the
instance_method/bind trick almost useless :(
Many thanks for the tip though.

-g.
 
G

George Moschovitis

Object methods do not work in vacuum, they usually use instance
variables to reffer to the object's state, possibly changing it
(remember encapsulation ;-)?). How would you expect your
inject_method() deal with it?

I thought Ruby promotes 'duck typing'.

I would like to inject specific methods between specific classes. The
destination class has all the instance variables needed to succesfully
execute the method.
 
G

George Moschovitis

Well, you can use #instance_method and #bind but ruby will make the
test

wow, this is magic!
-g.
 
G

Gennady Bystritksy

George said:
I thought Ruby promotes 'duck typing'.

I would like to inject specific methods between specific classes. The
destination class has all the instance variables needed to succesfully
execute the method.

I do not think what you want has anything to do with 'duck typing'.
'duck typing' only means that you can send a message to any object and
if the object has a corresponding method it gets executed regardless of
object type. It does not urge you to abandon the "normal" way of method
definition for a class.

In general, I am very cautious to dynamically adding methods down the
road, as it greatly increases the risk of your system becoming
incomprehensible.

As somebody has indicated, Ruby has other ways to "inject" methods in
some controlled manner, like mixins or inheritance. I would consider
them first before resorting to any sort of "tricks", even if they were
possible (everything is possible in Ruby, you know ;-), one way or another).

Gennady.
 
R

Robert Klemme

George Moschovitis said:
I thought Ruby promotes 'duck typing'.

I would like to inject specific methods between specific classes. The
destination class has all the instance variables needed to succesfully
execute the method.

And what about other methods that are used by a method? I think it's not
generally possible to move methods around.

But maybe this trick helps:

class Object
def cast(target_class)
copy = case target_class
when Class; target_class.allocate
when Module; dup
else target_class.class.allocate
end

instance_variables.each do |var|
copy.instance_variable_set(var, instance_variable_get(var))
end

copy
end
end

Then you can create a tmp copy of an instance with all instance variables
copied but a different class. So you can do
foo.cast(Bar).bar_method_to_be_called().

Cheers

robert
 
R

Robert Klemme

Robert Klemme said:
And what about other methods that are used by a method? I think it's not
generally possible to move methods around.

But maybe this trick helps:

Should've read

class Object
def cast(target_class)
copy = case target_class
when Class; target_class.allocate
when Module; dup.extend(target_class)
else target_class.class.allocate
end

instance_variables.each do |var|
copy.instance_variable_set(var, instance_variable_get(var))
end

copy
end
end
 
G

George Moschovitis

I do not think what you want has anything to do with 'duck typing'.

Of course it has. Have a look at the following example:

class C1
attr_accessor :a

def a1
puts @a
end

def a2
puts 'hello'
end
end

class C2
attr_accessor :a
end

So for method C1#a1 the class C2 'quacks like a duck' (ie, it has the
attribute @a)

So in 'duck typing' spirit, it should be possible to do:

c = C2.new

m = C1.instance_method:)a1)
m.bind(c)

c.a1

however Ruby raises a TypeError :(

Can anyone give me an example, where this #bind method is usefull? I
mean, you can only bind a Method back to an object of the original
method's Class or a Subclass. But such an object allready has this
method, so whats the point?
Am I missing something here?

regards,
George
 
R

Robert Klemme

Can anyone give me an example, where this #bind method is usefull? I
mean, you can only bind a Method back to an object of the original
method's Class or a Subclass. But such an object allready has this
method, so whats the point?
Am I missing something here?

Hm, maybe for caching:

@cached = lengthy_process_that_returns_an_unbound_method()

....

def treat(obj)
@cached.bind(obj).call
end

Dunno whether that's more efficient than just remembering the symbol and
invoking the method via send...

Kind regards

robert
 
G

George Moschovitis

Hm, maybe for caching:
@cached = lengthy_process_that_returns_an_unbound_method()
...
Dunno whether that's more efficient than just remembering the symbol and
invoking the method via send...

Robert,

thank you for taking the time to answer my post.

However, I dont think that this is a usefull example. One could use a
simple
obj.class.class_eval("..") to achieve the same.

Dont you feel that allowing bind to attach methods only to objects that
already have this method (ie, obj.kind_of(class) == true) is strange?
George.
 
F

Florian Gross

ts said:
G> however, I would like to use it carefully. My question was: is this
G> possible?

Well, you can use #instance_method and #bind but ruby will make the test

uln% ruby -e 'Array.instance_method:)[]).bind(Hash.new)'
-e:1:in `bind': bind argument must be an instance of Array (TypeError)

Note that EvilRuby has a less strict version of UnboundMethod#bind
(#force_bind) which will work in this case:

class Foo
def x() end
end

class Bar
end

Foo.instance_method:)x).bind(Bar.new) # raises TypeError
Foo.instance_method:)x).force_bind(Bar.new) # works
Hash.instance_method:)[]).force_bind(Array.new) # still doesn't work
 
F

Florian Gross

George said:
Can anyone give me an example, where this #bind method is usefull? I
mean, you can only bind a Method back to an object of the original
method's Class or a Subclass. But such an object allready has this
method, so whats the point?

If you're subclass provides an overwritten version of the base method
this trick still allows you to call the base one:

irb(main):012:0> Object.instance_method:)to_s).bind(2459).call
=> "#<Fixnum:0x1337>"
irb(main):013:0> Object.instance_method:)to_s).bind(nil).call
=> "#<NilClass:0x4>"

This can for example be used to implement a super() that skips over some
of the inheritance chain members (see attachment.) or for sand boxes
were you want to make sure that you are calling a secure method that is
defined in Object and not an eventually overwritten version of it.
 
F

Florian Gross

George said:
Is it possible to write 'inject_method' in Ruby ?

Apart from the ways that were already mentioned you can also do this:

obj = Class.new { def bar() puts "foo" }.new

class Foo
define_method:)bar, &obj.method:)bar))
end

But note that this is the same as doing this:

class Foo
define_method:)bar) { obj.method:)bar).call }
end

I've posted in this thread already that EvilRuby is less restrictive
with Method#force_bind, but I don't think that this is needed in your
case. (It could however be useful to implement AOP-like stuff.)
 
N

Nicholas Van Weerdenburg

I thought Ruby promotes 'duck typing'.

I would like to inject specific methods between specific classes. The
destination class has all the instance variables needed to succesfully
execute the method.

That's not duck typing, it's duck cloning, Dr. Frankenstein!
 

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,995
Messages
2,570,228
Members
46,816
Latest member
nipsseyhussle

Latest Threads

Top