Feature #4254

Allow method transplanting

Added by Jonas Pfenniger over 3 years ago. Updated 11 months ago.

[ruby-core:34267]
Status:Closed
Priority:Normal
Assignee:Yukihiro Matsumoto
Category:core
Target version:2.0.0

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(:definemethod, :foo, M.instancemethod(:foo)) #=> should not raise

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

0001-method-transplanting-Allow-to-set-an-UnboundMethod-t.patch Magnifier (1.07 KB) Jonas Pfenniger, 01/09/2011 09:58 PM

Associated revisions

Revision 36214
Added by Nobuyoshi Nakada almost 2 years ago

method transplanting

  • proc.c (rbmoddefine_method): allow method transplanting from a module to either class or module. [Feature #4254]

Revision 36215
Added by Nobuyoshi Nakada almost 2 years ago

method transplanting

  • proc.c (rbmoddefine_method): allow method transplanting from a module to either class or module. [Feature #4254]

Revision 38354
Added by Nobuyoshi Nakada over 1 year ago

method transplanting

  • proc.c (umethod_bind): allow another form of method transplanting from a module via UnboundMethod. [Feature #4254]

History

#1 Updated by Ryan Davis over 3 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

#2 Updated by Konstantin Haase over 3 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(:definemethod, :foo, M.instancemethod(:foo)) #=> should not raise

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


http://redmine.ruby-lang.org

=end

#3 Updated by Jonas Pfenniger over 3 years ago

=begin
2011/1/9 Haase, Konstantin Konstantin.Haase@student.hpi.uni-potsdam.de:

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

#4 Updated by Loren Segal over 3 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
"forcebind". You call .forcebind 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

#5 Updated by Loren Segal over 3 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
"forcebind". You call .forcebind 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

#6 Updated by Charles Nutter over 3 years ago

=begin
On Sun, Jan 9, 2011 at 1:53 PM, Loren Segal lsegal@soen.ca wrote:

FWIW I've implemented something similar a while back as a gem called
"forcebind". You call .forcebind 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

#7 Updated by Loren Segal over 3 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

#8 Updated by Yukihiro Matsumoto over 3 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 1.9-Feature#4254][Open] Allow method transplanting"
on Sun, 9 Jan 2011 21:58:16 +0900, Jonas Pfenniger redmine@ruby-lang.org 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(:definemethod, :foo, M.instancemethod(:foo)) #=> should not raise
|
|It's like monkey-patching. Powerful, dangerous, but also really useful. It could allow different variations of methodwrap or aliasmethodchain that are not possible right now.
|
|
|----------------------------------------
|http://redmine.ruby-lang.org
|[2 0001-method-transplanting-Allow-to-set-an-UnboundMethod-t.patch ]
|From 0336ec334f7eb66d2cf05bd7a29d748780d6044e Mon Sep 17 00:00:00 2001
|From: Jonas Pfenniger jonas@pfenniger.name
|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
moddefinemethod(int argc, VALUE *argv, VALUE mod)
| if (rbobjismethod(body)) {
| struct METHOD *method = (struct METHOD *)DATA
PTR(body);
| VALUE rclass = method->rclass;
|- if (rclass != mod && !RTEST(rbclassinheritedp(mod, rclass))) {
|- if (FL
TEST(rclass, FLSINGLETON)) {
|- rb
raise(rbeTypeError,
|- "can't bind singleton method to a different class");
|- }
|- else {
|- rb
raise(rbeTypeError,
|- "bind argument must be a subclass of %s",
|- rb
class2name(rclass));
|- }
|- }
| rbmethodentryset(mod, id, &method->me, noex);
| }
| else if (rb
objisproc(body)) {
|--
|1.7.3.4

=end

#9 Updated by Jonas Pfenniger over 3 years ago

=begin
2011/1/10 Yukihiro Matsumoto matz@ruby-lang.org:

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 DataWrapStruct ?

=end

#10 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

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

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

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

#11 Updated by Charles Nutter over 3 years ago

=begin
On Sun, Jan 9, 2011 at 7:14 PM, Yukihiro Matsumoto matz@ruby-lang.org wrote:

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

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

#12 Updated by Nobuyoshi Nakada over 3 years ago

=begin
Hi,

: At Mon, 10 Jan 2011 10:14:34 +0900,
: Yukihiro Matsumoto wrote in :
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 (rbmoddefine_method): allow method transplanting between modules.

diff --git i/proc.c w/proc.c
index 9ecf626..6affba6 100644
--- i/proc.c
+++ w/proc.c
@@ -1303,7 +1303,8 @@ rbmoddefinemethod(int argc, VALUE *argv, VALUE mod)
if (rb
objismethod(body)) {
struct METHOD *method = (struct METHOD *)DATAPTR(body);
VALUE rclass = method->rclass;
- if (rclass != mod && !RTEST(rb
classinheritedp(mod, rclass))) {
+ if (rclass != mod && !(RBTYPEP(rclass, TMODULE) && RBTYPEP(mod, TMODULE)) &&
+ !RTEST(rbclassinheritedp(mod, rclass))) {
if (FL
TEST(rclass, FLSINGLETON)) {
rb
raise(rbeTypeError,
"can't bind singleton method to a different class");
diff --git i/test/ruby/test
method.rb w/test/ruby/testmethod.rb
index fa5f998..0cbc785 100644
--- i/test/ruby/test
method.rb
+++ w/test/ruby/testmethod.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
assertraise(TypeError) do
Class.new.class
eval {definemethod(:meth, M.instancemethod(:meth))}
end
+
+ feature4254 = ''
+ m = Module.new
+ m.moduleeval {definemethod(:meth, M.instancemethod(:meth))}
+ assert
equal(:meth, Object.new.extend(m).meth, feature4254)
end

def test_clone

--- Nobu Nakada

=end

#13 Updated by Ryan Davis over 3 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

#14 Updated by Charles Nutter over 3 years ago

=begin
On Mon, Jan 10, 2011 at 1:15 AM, Ryan Davis ryand-ruby@zenspider.com 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

#15 Updated by Benoit Daloze over 3 years ago

=begin
Hi,
On 10 January 2011 02:14, Yukihiro Matsumoto matz@ruby-lang.org 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

#16 Updated by Loren Segal over 3 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

#17 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

In message "Re: Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 16:15:40 +0900, Ryan Davis ryand-ruby@zenspider.com 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

#18 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

In message "Re: Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 20:50:32 +0900, Benoit Daloze eregontp@gmail.com 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

#19 Updated by Ryan Davis over 3 years ago

=begin

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

Hi,

In message "Re: Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Mon, 10 Jan 2011 16:15:40 +0900, Ryan Davis ryand-ruby@zenspider.com 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(:definemethod, :foo, M.instancemethod(: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

#20 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

In message "Re: Re: [Ruby 1.9-Feature#4254][Open] Allow method transplanting"
on Tue, 11 Jan 2011 10:24:55 +0900, Ryan Davis ryand-ruby@zenspider.com 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 . 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

#21 Updated by Jonas Pfenniger over 3 years ago

=begin
2011/1/11 Yukihiro Matsumoto matz@ruby-lang.org:

See .  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
definemethod, 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

#22 Updated by Nobuyoshi Nakada over 3 years ago

=begin
Hi,

At Thu, 13 Jan 2011 01:48:33 +0900,
Jonas Pfenniger (zimbatm) wrote in :

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
definemethod, 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
afoo = instancemethod(:foo)
definemethod(: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

#23 Updated by Hiroshi Nakamura almost 3 years ago

  • Target version changed from 1.9.3 to 2.0.0

#24 Updated by Hiroshi Nakamura about 2 years ago

  • Status changed from Open to Rejected
  • Assignee set to Yukihiro Matsumoto

#25 Updated by Nobuyoshi Nakada almost 2 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

#26 Updated by Yukihiro Matsumoto almost 2 years ago

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

Matz.

#27 Updated by Martin Dürst almost 2 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.]

#28 Updated by Nobuyoshi Nakada almost 2 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 (rbmoddefine_method): allow method transplanting from a module to either class or module. [Feature #4254]

#29 Updated by Thomas Sawyer almost 2 years ago

What prevents methods from being transplanted from a class?

#30 Updated by Rodrigo Rosenfeld Rosas almost 2 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...

#31 Updated by Thomas Sawyer almost 2 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.

#32 Updated by Jim Gay 11 months 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.instancemethod(:themethod).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

#33 Updated by Nobuyoshi Nakada 11 months ago

=begin
Seems fine.

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

Please open new ticket if needed.
=end

#34 Updated by Nobuyoshi Nakada 11 months 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

#35 Updated by Jim Gay 11 months ago

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

Also available in: Atom PDF