Project

General

Profile

Actions

Feature #4254

closed

Allow method transplanting

Added by zimbatm (zimba tm) about 13 years ago. Updated almost 11 years ago.

Status:
Closed
Target version:
[ruby-core:34267]

Description

=begin
Is there a technical reason to not allow re-binding a method from one module to any other module ?

module M
def foo; "foo"; end
end

module N; end
N.send(:define_method, :foo, M.instance_method(:foo)) #=> should not raise

It's like monkey-patching. Powerful, dangerous, but also really useful. It could allow different variations of method_wrap or alias_method_chain that are not possible right now.
=end


Files

Actions #1

Updated by zenspider (Ryan Davis) about 13 years ago

=begin

On Jan 9, 2011, at 04:58 , Jonas Pfenniger wrote:

Feature #4254: Allow method transplanting
http://redmine.ruby-lang.org/issues/show/4254

Author: Jonas Pfenniger
Status: Open, Priority: Normal
Category: core, Target version: 1.9.3

Is there a technical reason to not allow re-binding a method from one module to any other module ?

I would like to see this approved. I think it could open up a lot of interesting programming styles we can't even currently consider. This would allow for instance-oriented programming among other things.

=end

Actions #2

Updated by rkh (Konstantin Haase) about 13 years ago

=begin
Wouldn't that be unnecessary if matz' trait proposal would be implemented?

On Jan 9, 2011, at 13:58 , Jonas Pfenniger wrote:

Feature #4254: Allow method transplanting
http://redmine.ruby-lang.org/issues/show/4254

Author: Jonas Pfenniger
Status: Open, Priority: Normal
Category: core, Target version: 1.9.3

Is there a technical reason to not allow re-binding a method from one module to any other module ?

module M
def foo; "foo"; end
end

module N; end
N.send(:define_method, :foo, M.instance_method(:foo)) #=> should not raise

It's like monkey-patching. Powerful, dangerous, but also really useful. It could allow different variations of method_wrap or alias_method_chain that are not possible right now.


http://redmine.ruby-lang.org
<0001-method-transplanting-Allow-to-set-an-UnboundMethod-t.patch>

=end

Actions #3

Updated by zimbatm (zimba tm) about 13 years ago

=begin
2011/1/9 Haase, Konstantin :

Wouldn't that be unnecessary if matz' trait proposal would be implemented?

I agree it's not the cleanest, but it's not like I would be adding any
feature. rubyspec doesn't give me any addition error, expect the one
that explicitly tests for the exception.

What I'm more worried about, is if other ruby implementations can come
up with a working solution. For example if instance variables are
transformed into an array index at compile-time (rubinius?), then the
index might not be the same in the other object.

=end

Actions #4

Updated by lsegal (Loren Segal) about 13 years ago

=begin
On 1/9/2011 8:17 AM, Jonas Pfenniger (zimbatm) wrote:

I agree it's not the cleanest, but it's not like I would be adding any
feature. rubyspec doesn't give me any addition error, expect the one
that explicitly tests for the exception.

FWIW I've implemented something similar a while back as a gem called
"force_bind". You call .force_bind on an UnboundMethod instead of bind.

https://rubygems.org/gems/force_bind

Although it would be great to see #bind (and define_method) support this
out of the box, adding this behaviour through a gem isn't so bad.

=end

Actions #5

Updated by lsegal (Loren Segal) about 13 years ago

=begin

On 1/9/2011 2:53 PM, Loren Segal wrote:

FWIW I've implemented something similar a while back as a gem called
"force_bind". You call .force_bind on an UnboundMethod instead of bind.

By the way, if this patch is accepted, we should also patch
UnboundMethod#bind to allow binding to different module/classes-- as
force_bind does-- to keep things consistent.

=end

Actions #6

Updated by headius (Charles Nutter) about 13 years ago

=begin
On Sun, Jan 9, 2011 at 1:53 PM, Loren Segal wrote:

FWIW I've implemented something similar a while back as a gem called
"force_bind". You call .force_bind on an UnboundMethod instead of bind.

https://rubygems.org/gems/force_bind

Although it would be great to see #bind (and define_method) support this out
of the box, adding this behaviour through a gem isn't so bad.

JRuby has shipped this feature since 2006 or so, called
JRubyExtensions.steal_method(target, source, :name). We originally did
it because we needed only some methods from ActiveRecord on our hacked
adapters around JDBC. I'm unsure if it's used anymore.

  • Charlie

=end

Actions #7

Updated by lsegal (Loren Segal) about 13 years ago

=begin

On 2011-01-09, at 2:53 PM, Loren Segal wrote:

Although it would be great to see #bind (and define_method) support this out of the box, adding this behaviour through a gem isn't so bad.

I just gave force_bind a shot after having not used it in a long while, and I found out that it's no longer working in 1.9.2+. I originally wrote it when 1.9.1 was latest. It seems that the method structures have changed quite a bit since then, and that means it's possible they might change more in the future.

I therefore go back on what I said above: supporting this feature as a gem doesn't really seem to be a maintainable solution. Official support would be much better.

=end

Actions #8

Updated by matz (Yukihiro Matsumoto) about 13 years ago

=begin
Hi,

Class to class method transplanting could cause serious problem, but
it might be able to relax module to module transplanting.

						matz.

In message "Re: [ruby-core:34267] [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Sun, 9 Jan 2011 21:58:16 +0900, Jonas Pfenniger writes:

|Author: Jonas Pfenniger
|Status: Open, Priority: Normal
|Category: core, Target version: 1.9.3
|
|Is there a technical reason to not allow re-binding a method from one module to any other module ?
|
| module M
| def foo; "foo"; end
| end
|
| module N; end
| N.send(:define_method, :foo, M.instance_method(:foo)) #=> should not raise
|
|It's like monkey-patching. Powerful, dangerous, but also really useful. It could allow different variations of method_wrap or alias_method_chain that are not possible right now.
|
|
|----------------------------------------
|http://redmine.ruby-lang.org
|[2 0001-method-transplanting-Allow-to-set-an-UnboundMethod-t.patch <text/plain (base64)>]
|From 0336ec334f7eb66d2cf05bd7a29d748780d6044e Mon Sep 17 00:00:00 2001
|From: Jonas Pfenniger
|Date: Sun, 9 Jan 2011 00:13:57 +0000
|Subject: [PATCH] method transplanting: Allow to set an UnboundMethod to any module.
|
|It's time to grow up, remove the security nets.
|---
| proc.c | 11 -----------
| 1 files changed, 0 insertions(+), 11 deletions(-)
|
|diff --git a/proc.c b/proc.c
|index 7df2ec8..652b3e1 100644
|--- a/proc.c
|+++ b/proc.c
|@@ -1293,17 +1293,6 @@ rb_mod_define_method(int argc, VALUE *argv, VALUE mod)
| if (rb_obj_is_method(body)) {
| struct METHOD *method = (struct METHOD *)DATA_PTR(body);
| VALUE rclass = method->rclass;
|- if (rclass != mod && !RTEST(rb_class_inherited_p(mod, rclass))) {
|- if (FL_TEST(rclass, FL_SINGLETON)) {
|- rb_raise(rb_eTypeError,
|- "can't bind singleton method to a different class");
|- }
|- else {
|- rb_raise(rb_eTypeError,
|- "bind argument must be a subclass of %s",
|- rb_class2name(rclass));
|- }
|- }
| rb_method_entry_set(mod, id, &method->me, noex);
| }
| else if (rb_obj_is_proc(body)) {
|--
|1.7.3.4

=end

Actions #9

Updated by zimbatm (zimba tm) about 13 years ago

=begin
2011/1/10 Yukihiro Matsumoto :

Hi,

Class to class method transplanting could cause serious problem, but
it might be able to relax module to module transplanting.

                                                       matz.

Hi matz,

Are you thinking of a method is defined in C that uses Data_Wrap_Struct ?

=end

Actions #10

Updated by matz (Yukihiro Matsumoto) about 13 years ago

=begin
Hi,

In message "Re: [ruby-core:34281] Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 09:37:07 +0900, "Jonas Pfenniger (zimbatm)" writes:

|Are you thinking of a method is defined in C that uses Data_Wrap_Struct ?

No, I am thinking of methods defined for modules, that have no
assumption of the type of receivers. Ruby methods in C does not check
the type of the receiver, so that if you move a method from String
class to, say, Array, the interpreter will crash when you call the
transplanted method.

						matz.

=end

Actions #11

Updated by headius (Charles Nutter) about 13 years ago

=begin
On Sun, Jan 9, 2011 at 7:14 PM, Yukihiro Matsumoto wrote:

|Are you thinking of a method is defined in C that uses Data_Wrap_Struct ?

No, I am thinking of methods defined for modules, that have no
assumption of the type of receivers.  Ruby methods in C does not check
the type of the receiver, so that if you move a method from String
class to, say, Array, the interpreter will crash when you call the
transplanted method.

The same applies in JRuby for a slightly different reason: methods on
core classes are usually defined as instance methods on Java-based
classes like RubyArray or RubyString. Transplanting them to another
class would, for example, attempt to call a RubyArray method against a
RubyString instance, and throw a Java ClassCastExeption.
Essentially...it would crash :)

This is also the reason JRuby does not change the class of the IO
object when doing reopen; the concrete Java type is set at
construction time, and can't be changed (e.g. a file is RubyFile, and
always will be).

  • Charlie

=end

Actions #12

Updated by nobu (Nobuyoshi Nakada) about 13 years ago

=begin
Hi,

: At Mon, 10 Jan 2011 10:14:34 +0900,
: Yukihiro Matsumoto wrote in [ruby-core:34283]:
No, I am thinking of methods defined for modules, that have no
assumption of the type of receivers. Ruby methods in C does not check
the type of the receiver, so that if you move a method from String
class to, say, Array, the interpreter will crash when you call the
transplanted method.

Just between modules?

  • proc.c (rb_mod_define_method): allow method transplanting
    between modules. [ruby-core:34267]

diff --git i/proc.c w/proc.c
index 9ecf626..6affba6 100644
--- i/proc.c
+++ w/proc.c
@@ -1303,7 +1303,8 @@ rb_mod_define_method(int argc, VALUE *argv, VALUE mod)
if (rb_obj_is_method(body)) {
struct METHOD *method = (struct METHOD *)DATA_PTR(body);
VALUE rclass = method->rclass;

  • if (rclass != mod && !RTEST(rb_class_inherited_p(mod, rclass))) {
  • if (rclass != mod && !(RB_TYPE_P(rclass, T_MODULE) && RB_TYPE_P(mod, T_MODULE)) &&
  •  !RTEST(rb_class_inherited_p(mod, rclass))) {
     if (FL_TEST(rclass, FL_SINGLETON)) {
     rb_raise(rb_eTypeError,
     	 "can't bind singleton method to a different class");
    

diff --git i/test/ruby/test_method.rb w/test/ruby/test_method.rb
index fa5f998..0cbc785 100644
--- i/test/ruby/test_method.rb
+++ w/test/ruby/test_method.rb
@@ -36,7 +36,7 @@ class TestMethod < Test::Unit::TestCase
module M
def func; end
module_function :func

  • def meth; end
  • def meth; :meth end
    end
def mv1() end

@@ -205,6 +205,11 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) do
Class.new.class_eval {define_method(:meth, M.instance_method(:meth))}
end
+

  • feature4254 = '[ruby-core:34267]'
  • m = Module.new
  • m.module_eval {define_method(:meth, M.instance_method(:meth))}
  • assert_equal(:meth, Object.new.extend(m).meth, feature4254)
    end
def test_clone

--- Nobu Nakada

=end

Actions #13

Updated by zenspider (Ryan Davis) about 13 years ago

=begin

On Jan 9, 2011, at 16:05 , Yukihiro Matsumoto wrote:

Class to class method transplanting could cause serious problem, but
it might be able to relax module to module transplanting.

Matz,

You've argued that being able to define != contradictory with == (a serious problem in my mind) is ok because we trust rubyists to do the right thing. How is this any different? If the method transplanted is pure ruby, why not allow it?

=end

Actions #14

Updated by headius (Charles Nutter) about 13 years ago

=begin
On Mon, Jan 10, 2011 at 1:15 AM, Ryan Davis wrote:

Matz,

You've argued that being able to define != contradictory with == (a serious problem in my mind) is ok because we trust rubyists to do the right thing. How is this any different? If the method transplanted is pure ruby, why not allow it?

Pure Ruby methods would be fine in JRuby as well. They're just bags of
code at that point, and can only see the object's
structure/representation as much as any other Ruby code. They might do
things that cause Ruby exceptions to raise, but probably not crash.

  • Charlie

=end

Actions #15

Updated by Eregon (Benoit Daloze) about 13 years ago

=begin
Hi,
On 10 January 2011 02:14, Yukihiro Matsumoto wrote:

Hi,
Class to class method transplanting could cause serious problem, but
it might be able to relax module to module transplanting.
                                                       matz.

As Ryan mentioned, I think it is a danger a developer using #bind can live with.

The best would be able to check if it is actually impossible (so
detect if a method is implemented in C), and raise in that case.
I do not know if that is possible and reliable. (eg:
Method#source_location.nil?)

But I think it is best allowed for all than none class methods.

=end

Actions #16

Updated by lsegal (Loren Segal) about 13 years ago

=begin

On 1/10/2011 2:15 AM, Ryan Davis wrote:

If the method transplanted is pure ruby, why not allow it?

I should follow up on this because I was thinking the same thing. Is
there a way to detect that a method is "native"? If so, we should only
block definitions/rebinds on methods that are native code. FWIW
force_bind has an example or two that rebinds instance methods of
classes (pure-ruby) to other classes and they work quite well, even when
interacting with class things like @ivars.

=end

Actions #17

Updated by matz (Yukihiro Matsumoto) about 13 years ago

=begin
Hi,

In message "Re: [ruby-core:34291] Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 16:15:40 +0900, Ryan Davis writes:

|You've argued that being able to define != contradictory with == (a serious problem in my mind) is ok because we trust rubyists to do the right thing. How is this any different? If the method transplanted is pure ruby, why not allow it?

Although I trust the programmers, we have another rule for the
interpreter, that Ruby should not cause segmentation fault from any
Ruby program.

						matz.

=end

Actions #18

Updated by matz (Yukihiro Matsumoto) about 13 years ago

=begin
Hi,

In message "Re: [ruby-core:34303] Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 20:50:32 +0900, Benoit Daloze writes:

|As Ryan mentioned, I think it is a danger a developer using #bind can live with.
|
|The best would be able to check if it is actually impossible (so
|detect if a method is implemented in C), and raise in that case.
|I do not know if that is possible and reliable. (eg:
|Method#source_location.nil?)
|
|But I think it is best allowed for all than none class methods.

I disagree. The implementation language of a method may change
between versions. Relying on that may make programs fragile.
Trusting programmers should not be excuse for making Ruby a dangerous
place for the programmers.

						matz.

=end

Actions #19

Updated by zenspider (Ryan Davis) about 13 years ago

=begin

On Jan 10, 2011, at 14:30 , Yukihiro Matsumoto wrote:

Hi,

In message "Re: [ruby-core:34291] Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 16:15:40 +0900, Ryan Davis writes:

|You've argued that being able to define != contradictory with == (a serious problem in my mind) is ok because we trust rubyists to do the right thing. How is this any different? If the method transplanted is pure ruby, why not allow it?

Although I trust the programmers, we have another rule for the
interpreter, that Ruby should not cause segmentation fault from any
Ruby program.

How can transplanting a pure-ruby method cause a segfault?

I see nothing in the original example that could cause that:

module M
def foo; "foo"; end
end

module N; end
N.send(:define_method, :foo, M.instance_method(:foo)) #=> should not raise

When we were trying it we were using bind on instances, not define_method on classes/modules. That seems even safer.

=end

Actions #20

Updated by matz (Yukihiro Matsumoto) about 13 years ago

=begin
Hi,

In message "Re: [ruby-core:34319] Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Tue, 11 Jan 2011 10:24:55 +0900, Ryan Davis writes:

|> Although I trust the programmers, we have another rule for the
|> interpreter, that Ruby should not cause segmentation fault from any
|> Ruby program.
|
|How can transplanting a pure-ruby method cause a segfault?

See [ruby-core:34319]. From above rule we cannot allow all method
transplanting from classes. Since we don't want program fragility
between versions, I don't want to allow method transplanting from
classes at all.

Remember the beginning of 1.8.7; rather small incompatibility hindered
the reputation of the release so bad. I don't want to see the
situation like that again.

						matz.

=end

Actions #21

Updated by zimbatm (zimba tm) about 13 years ago

=begin
2011/1/11 Yukihiro Matsumoto :

See [ruby-core:34319].  From above rule we cannot allow all method
transplanting from classes.  Since we don't want program fragility
between versions, I don't want to allow method transplanting from
classes at all.

Remember the beginning of 1.8.7; rather small incompatibility hindered
the reputation of the release so bad.  I don't want to see the
situation like that again.

I agree with matz. If it's something we want for the next releases,
then it should not break anything. Having transplantable Module
methods already unlocks lots of scenarios, where a module can act as a
bag of methods. For example it allows using methods with
define_method, which allows having regular argument-passing, in
contract with blocks, which capture the current context and don't
allow a block as argument. This in turn allows
to find new patterns that can replace alias_method_chain. If we have that,
then it's already wonderful.

For the ones who are not satisfied with the current scope of the
ticket, I propose to open a new 2.0+ issue for the research that is
needed to come up for a universal solution.

=end

Actions #22

Updated by nobu (Nobuyoshi Nakada) about 13 years ago

=begin
Hi,

At Thu, 13 Jan 2011 01:48:33 +0900,
Jonas Pfenniger (zimbatm) wrote in [ruby-core:34444]:

I agree with matz. If it's something we want for the next releases,
then it should not break anything. Having transplantable Module
methods already unlocks lots of scenarios, where a module can act as a
bag of methods. For example it allows using methods with
define_method, which allows having regular argument-passing, in
contract with blocks, which capture the current context and don't
allow a block as argument. This in turn allows
to find new patterns that can replace alias_method_chain. If we have that,
then it's already wonderful.

1.9 already allows a block having block-passing.

class A
def foo
yield "A"
end
end

class B < A
a_foo = instance_method(:foo)
define_method(:foo) do |&block|
a_foo.bind(self).call {|s| block.call(s+"B")}
end
end

B.new.foo {|x|p x} #=> "AB"

--
Nobu Nakada

=end

Updated by nahi (Hiroshi Nakamura) over 12 years ago

  • Target version changed from 1.9.3 to 2.0.0

Updated by nahi (Hiroshi Nakamura) about 12 years ago

  • Status changed from Open to Rejected
  • Assignee set to matz (Yukihiro Matsumoto)

Updated by nobu (Nobuyoshi Nakada) almost 12 years ago

  • Description updated (diff)
  • Status changed from Rejected to Assigned

=begin
What status is this proposal?

I don't think the original proposal has been rejected, but only other
additional extensions.

Possible choice would be:

(1) keep current behavior, all method transplanting is prohibited.

(2) accept the original proposal only, transplanting is allowed only
between modules but disallowed between class and module/class.

(3) still look for "relaxed" condition, actually same as (1) but leave
this ticket opened.

(4) others.

I think there is no probable "relaxed" versions, though.
=end

Updated by matz (Yukihiro Matsumoto) almost 12 years ago

I'd like to allow method transplanting from a module, not a class, to either class or module.
Any objection?

Matz.

Updated by duerst (Martin Dürst) almost 12 years ago

matz (Yukihiro Matsumoto) wrote:

I'd like to allow method transplanting from a module, not a class, to either class or module.
Any objection?

I think this is a step in the right direction. Both JavaScript and Python allow functions (including what are essentially methods) to be assigned arbitrarily. There are often better ways to achieve the same thing in Ruby, but sometimes, there's not much of an alternative.

[Some of my students were working on a project to automatically convert Python programs to Ruby, and this is a very hard wall we bumped into.]

Actions #28

Updated by nobu (Nobuyoshi Nakada) almost 12 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r36214.
Jonas, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


method transplanting

  • proc.c (rb_mod_define_method): allow method transplanting from a
    module to either class or module. [ruby-core:34267][Feature #4254]

Updated by trans (Thomas Sawyer) almost 12 years ago

What prevents methods from being transplanted from a class?

Updated by rosenfeld (Rodrigo Rosenfeld Rosas) almost 12 years ago

I guess it wouldn't make much sense and would lead to other developer's confusion as the method may rely on some internal state of an instance of that class...

Updated by trans (Thomas Sawyer) almost 12 years ago

That makes sense. But I am not sure it matters a great deal. Modules too can have interdependent methods and reference instance variables. I get the concept, but ultimately I'd just prefer to have full flexibility and be done with it, rather than having to fuss with the limitations. This is a meta-programming feature and like all such techniques it requires care by the developer.

However, I am also coming at this with very particular use case in mind. I would make little girl squeally noises to be able to define a method that could include a module that would also include it's class methods. Being able to transplant class methods would make that possible (albeit not the most elegant approach). If there were another means of doing that, then I wouldn't care as much about transplanting class methods.

Updated by saturnflyer (Jim Gay) almost 11 years ago

=begin
I am interested in this feature although with a different use.

It appears that 2.0.0-p0 allowed methods to be unbound from a module and bound to any object.

I've forked rubyspec and created a failing spec ((URL:https://github.com/saturnflyer/rubyspec/compare/a4b320efc34c...668f4be5f99852a))

In 2.0.0-p0 this code works: (({SomeModule.instance_method(:the_method).bind(Object.new)})) but this seems to be broken in 2.1-dev (and is not present in previous versions)

This project uses this feature to temporarily add behavior to an object ((URL:https://github.com/saturnflyer/casting)) and Travis CI is running the tests at ((URL:https://travis-ci.org/saturnflyer/casting/builds/7432955))

The specs fail on ruby-head ((URL:https://travis-ci.org/saturnflyer/casting/jobs/7432959)) but pass for 2.0 ((URL:https://travis-ci.org/saturnflyer/casting/jobs/7432957))

This feature is useful in allowing an object to run methods inside a block:

object.hello_world #=> NoMethodError

Casting.delegating(object => GreetingModule) do
object.hello_world #=> "Hello world!"
end

object.hello_world #=> NoMethodError

The above example uses method_missing to unbind methods from the given module then bind them to self and call them.

If there is a better place to post this, or if I should open a new ticket, please correct me.

=end

Updated by nobu (Nobuyoshi Nakada) almost 11 years ago

=begin
Seems fine.

$ ruby -v -e 'module M; def foo; :foo;end; end; p m = M.instance_method(:foo).bind(Object.new); p m.call'
ruby 2.1.0dev (2013-05-25 trunk 40923) [universal.x86_64-darwin11]
#<Method: Object(M)#foo>
:foo

Please open new ticket if needed.
=end

Updated by nobu (Nobuyoshi Nakada) almost 11 years ago

=begin
: saturnflyer (Jim Gay) wrote:
The specs fail on ruby-head ((URL:https://travis-ci.org/saturnflyer/casting/jobs/7432959)) but pass for 2.0 ((URL:https://travis-ci.org/saturnflyer/casting/jobs/7432957))

From your "ruby-head" log:

$ ruby --version
ruby 2.0.0dev (2012-12-12) [x86_64-linux]

It's too old.
=end

Updated by saturnflyer (Jim Gay) almost 11 years ago

thanks nobu! I blindly trusted the travis-ci build. good to know this is still valid behavior.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0