Hi all!
This is my first post here, so please don't yell at me too harshly :-)
I have a little problem with 'super' keyword when it is used in
define_method/alias_method. First, a little example:
class C
def greetings
p "Hello, #{caller.first}"
end
def curses
p "Damn you, #{caller.first}"
end
end
class D < C
def greetings
super
end
define_method :curses, instance_method(:greetings)
end
[3] pry(main)> D.new.greetings
"Hello, (pry):11:in `greetings'"
=> "Hello, (pry):11:in `greetings'"
[4] pry(main)> D.new.curses
"Hello, (pry):11:in `greetings'"
=> "Hello, (pry):11:in `greetings'"
So, as you can see 'super' is bound, perhaps a little unexpectedly, to
a method instance rather than to its call site. You can see it clearly
when inspecting the caller - in both cases it is 'greetings'. While
this may seem logical and sound, it is quite troubling when you try to
make a wrapper method around user-defined method doing metaprogramming
tricks. For example, I am trying to implement the chain pattern like
this:
def chain
new_method_name = "__#{@__chain_method}"
chained_method = @__chain_method
#....
alias_method new_method_name, chained_method
private new_method_name
remove_method chained_method
define_method(chained_method) do |*args|
send(new_method_name, *args) do |*yielded_args|
#...
end
end unless method_defined? chained_method
#...
end
In short, when a new method of given name is defined I alias it to
some other name (__old_name) and then I define another method
replacing user-defined method. In pseudo-code
def execute
#...
end
ends up as
def __execute; end;
def execute; ... send(__execute) ...; end;
Works like a charm, except when original method contains super - it is
supposed to call superclass' __execute, but it calls superclass
execute instead and all this ends up with stack overflow (intended
callchain should be: execute -> __execute(child) -->
__execute(parent), but is: execute -> __execute(child) -> execute ->
__execute(child)).
Do you know any workaround? How can I overcome this problem and let
super be bound correctly if used from aliased method? Thanks in
advance for your opinions.
on 2013-03-18 17:53
on 2013-03-18 19:16
On Mon, Mar 18, 2013 at 5:50 PM, Marcin Rzeźnicki <marcin.rzeznicki@gmail.com> wrote: > Hi all! > This is my first post here, so please don't yell at me too harshly :-) Welcome to the wonderful world of Ruby! > end > => "Hello, (pry):11:in `greetings'" > [4] pry(main)> D.new.curses > "Hello, (pry):11:in `greetings'" > => "Hello, (pry):11:in `greetings'" > > So, as you can see 'super' is bound, perhaps a little unexpectedly, to > a method instance rather than to its call site. Hmm, I'd still say it's the call site since the call site sits in method D#greeting even though the method is also known as D#curse. > You can see it clearly > when inspecting the caller - in both cases it is 'greetings'. While > this may seem logical and sound, it is quite troubling when you try to > make a wrapper method around user-defined method doing metaprogramming > tricks. > Do you know any workaround? How can I overcome this problem and let > super be bound correctly if used from aliased method? Thanks in > advance for your opinions. I am not sure what you expect should be called "bound correctly". Basically what you did is you said D#curse should be D#greeting (either via define_method as shown above or alias). That means, D's method #curse behaves like D's #greeting and hence ultimately invokes C#greeting and not C#curse. Btw, somehow your chain example seems incomplete: is #chain a method defined in Module? What about method names? I would have expected the chained method name to be given as argument. I once coded something together for wrapping methods - you may find it in the archives. I am not sure though that I still have the code elsewhere... I only found this one: https://gist.github.com/rklemme/3002732 but that's not the one I mean. Kind regards robert
on 2013-03-18 20:38
Robert Klemme wrote in post #1102151: > On Mon, Mar 18, 2013 at 5:50 PM, Marcin Rzeźnicki > <marcin.rzeznicki@gmail.com> wrote: >> Hi all! >> This is my first post here, so please don't yell at me too harshly :-) > > Welcome to the wonderful world of Ruby! > Thank you :-) >> >> So, as you can see 'super' is bound, perhaps a little unexpectedly, to >> a method instance rather than to its call site. > > Hmm, I'd still say it's the call site since the call site sits in > method D#greeting even though the method is also known as D#curse. > Yes, maybe I picked wrong terminology in my example. What I meant was that even though this method object is still the same, it is being called as something different (I'd say - method instance is simply reused but that's an implementation detail for me, so I'd prefer it to be masked out), so I would expect call frames to represent that. >> You can see it clearly >> when inspecting the caller - in both cases it is 'greetings'. While >> this may seem logical and sound, it is quite troubling when you try to >> make a wrapper method around user-defined method doing metaprogramming >> tricks. > >> Do you know any workaround? How can I overcome this problem and let >> super be bound correctly if used from aliased method? Thanks in >> advance for your opinions. > > I am not sure what you expect should be called "bound correctly". > Basically what you did is you said D#curse should be D#greeting > (either via define_method as shown above or alias). That means, D's > method #curse behaves like D's #greeting and hence ultimately invokes > C#greeting and not C#curse. > When I imagine hypothetical pseudo-code implementation of define_method I'd rather see: object[:method_name] = method_instance.code, than object[:method_name] = method_instance :-) > Btw, somehow your chain example seems incomplete: is #chain a method > defined in Module? What about method names? I would have expected > the chained method name to be given as argument. > Ah yes, I omitted lots of details for brevity, trying to get to the bottom line, if you will. Basically, chain is called from method_added hook. When you include the module it installs this hook, adds class method to specify the name of a "chained" method, listens for subclassing to get into child class to do the trick again etc., but I felt that this boilerplate was irrelevant to my problem. > I once coded something together for wrapping methods - you may find it > in the archives. I am not sure though that I still have the code > elsewhere... I only found this one: > https://gist.github.com/rklemme/3002732 but that's not the one I mean. > I will certainly try to find it. Many thanks. BTW, judging from example you posted (I have not yet run it, though) it exhibits the same problem as my implementation - calling super from #_m would call #m in parent class which could call #_m in child again. -- Greetings Marcin Rzeźnicki
on 2013-03-18 22:39
On Mon, Mar 18, 2013 at 8:39 PM, Marcin R. <lists@ruby-forum.com> wrote: > Robert Klemme wrote in post #1102151: >> On Mon, Mar 18, 2013 at 5:50 PM, Marcin Rzeźnicki >> I am not sure what you expect should be called "bound correctly". >> Basically what you did is you said D#curse should be D#greeting >> (either via define_method as shown above or alias). That means, D's >> method #curse behaves like D's #greeting and hence ultimately invokes >> C#greeting and not C#curse. > > When I imagine hypothetical pseudo-code implementation of define_method > I'd rather see: object[:method_name] = method_instance.code, than > object[:method_name] = method_instance :-) Your expectation was clear. But others may have different expectations. We are discussing to try to find out what might be more reasonable or what expectations might be shared by more people. :-) That's why I questioned whether the term "correctly" was appropriate here. >> Btw, somehow your chain example seems incomplete: is #chain a method >> defined in Module? What about method names? I would have expected >> the chained method name to be given as argument. > > Ah yes, I omitted lots of details for brevity, trying to get to the > bottom line, if you will. Basically, chain is called from method_added > hook. When you include the module it installs this hook, adds class > method to specify the name of a "chained" method, listens for > subclassing to get into child class to do the trick again etc., but I > felt that this boilerplate was irrelevant to my problem. Maybe you left out too many details. :-) It usually helps to provide a small and above all complete example that others can execute: http://sscce.org/ >> I once coded something together for wrapping methods - you may find it >> in the archives. I am not sure though that I still have the code >> elsewhere... I only found this one: >> https://gist.github.com/rklemme/3002732 but that's not the one I mean. > > I will certainly try to find it. Many thanks. BTW, judging from example > you posted (I have not yet run it, though) it exhibits the same problem > as my implementation - calling super from #_m would call #m in parent > class which could call #_m in child again. Yes, super and alias don't mix well. You could argue that a method's behavior would change depending on the name used to invoke it if Ruby was made to match your expectation. There are two downsides: 1. Could be viewed as an aesthetic issue: the behavior of code would change after the definition of the method. 2. As a consequence of changed behavior there would be a runtime effect: while executing the interpreter would have to have knowledge of the name used to invoke a method. Now you could argue that this knowledge does actually exist (caller provides it) but it may incur a runtime hit - for just a small amount of use cases. Redefining a super class method also affects what code "super" will invoke: irb(main):001:0> class A irb(main):002:1> def x; p 1 end irb(main):003:1> end => nil irb(main):004:0> class B < A irb(main):005:1> def x; p 2; super end irb(main):006:1> end => nil irb(main):007:0> B.new.x 2 1 => 1 irb(main):008:0> class A irb(main):009:1> alias _x x irb(main):010:1> def x; p 3; _x end irb(main):011:1> end => nil irb(main):012:0> B.new.x 2 3 1 => 1 irb(main):013:0> A.new.x 3 1 => 1 And then there is the issue that you can invoke a method without a name. What method would super invoke in that case? irb(main):024:0> m = B.instance_method :x => #<UnboundMethod: B#x> irb(main):025:0> b = B.new => #<B:0x88d0f38> irb(main):026:0> m.bind(b).call 2 3 1 => 1 With a slightly different experiment you can see that aliasing a method just creates a second reference to the same code: irb(main):001:0> class X irb(main):002:1> def x; caller 0 end irb(main):003:1> alias y x irb(main):004:1> end => nil irb(main):006:0> puts X.instance_method(:x).bind(X.new).call (irb):2:in `x' (irb):6:in `call' (irb):6:in `irb_binding' /usr/lib/ruby/1.9.1/irb/workspace.rb:80:in `eval' ... irb(main):007:0> puts X.instance_method(:y).bind(X.new).call (irb):2:in `x' (irb):7:in `call' (irb):7:in `irb_binding' /usr/lib/ruby/1.9.1/irb/workspace.rb:80:in `eval' ... Even for such a seemingly simple case if one goes beyond one's expectations things are more complex than they seem on initial sight. Kind regards robert
on 2013-03-19 14:46
Robert Klemme wrote in post #1102170: > On Mon, Mar 18, 2013 at 8:39 PM, Marcin R. <lists@ruby-forum.com> wrote: >> Robert Klemme wrote in post #1102151: >>> On Mon, Mar 18, 2013 at 5:50 PM, Marcin Rzeźnicki > Sorry for late answer, busy day at work. >>> I am not sure what you expect should be called "bound correctly". >>> Basically what you did is you said D#curse should be D#greeting >>> (either via define_method as shown above or alias). That means, D's >>> method #curse behaves like D's #greeting and hence ultimately invokes >>> C#greeting and not C#curse. >> >> When I imagine hypothetical pseudo-code implementation of define_method >> I'd rather see: object[:method_name] = method_instance.code, than >> object[:method_name] = method_instance :-) > > Your expectation was clear. But others may have different > expectations. We are discussing to try to find out what might be more > reasonable or what expectations might be shared by more people. :-) > That's why I questioned whether the term "correctly" was appropriate > here. Well, you're right - my wording implied that existing behavior is somehow 'incorrect' or buggy, while certainly it works as intended. Sorry for this. > >>> Btw, somehow your chain example seems incomplete: is #chain a method >>> defined in Module? What about method names? I would have expected >>> the chained method name to be given as argument. >> >> Ah yes, I omitted lots of details for brevity, trying to get to the >> bottom line, if you will. Basically, chain is called from method_added >> hook. When you include the module it installs this hook, adds class >> method to specify the name of a "chained" method, listens for >> subclassing to get into child class to do the trick again etc., but I >> felt that this boilerplate was irrelevant to my problem. > > Maybe you left out too many details. :-) It usually helps to provide > a small and above all complete example that others can execute: > http://sscce.org/ > Ah, it's nowhere near finished :-) I think that we should stick to greetings/curses problem, that's my problem distilled. >> I will certainly try to find it. Many thanks. BTW, judging from example >> you posted (I have not yet run it, though) it exhibits the same problem >> as my implementation - calling super from #_m would call #m in parent >> class which could call #_m in child again. > > Yes, super and alias don't mix well. You could argue that a method's > behavior would change depending on the name used to invoke it if Ruby > was made to match your expectation. There are two downsides: > > 1. Could be viewed as an aesthetic issue: the behavior of code would > change after the definition of the method. Yes, well, it's not that I want whole Ruby to be changed because of my whining. I guess what I was trying to ask was if there is some obvious solution that I overlooked (you know, kind of: "Geez, yet another rookie having problems with 'super'. Listen man, do yourself a favor and do it like this and this, it's been solved 1000 times before") > 2. As a consequence of changed behavior there would be a runtime > effect: while executing the interpreter would have to have knowledge > of the name used to invoke a method. Now you could argue that this > knowledge does actually exist (caller provides it) but it may incur a > runtime hit - for just a small amount of use cases. > I don't think so, I mean, obviously super already has to know the name of the method it's invoked from to do a lookup. The question is - where the name is taken from. If method used its "actual" name instead of "original" name then we're fine with super itself. An additional cost I can see lurking here is that define/alias method would need to copy a method instance (if called with one as an argument) and change its name to reflect aliasing, rather than just reference the original one. Anyhow, this is purely hypothetical. BTW, having given it a little thought I think that I can easily check for the condition of being called from super myself. What it takes is just a flag that signals that we're recursively called and a check what real 'self' is. I still need to flesh out the details but I guess this might work.
on 2013-03-19 15:33
On Tue, Mar 19, 2013 at 2:46 PM, Marcin R. <lists@ruby-forum.com> wrote: > Robert Klemme wrote in post #1102170: >> On Mon, Mar 18, 2013 at 8:39 PM, Marcin R. <lists@ruby-forum.com> wrote: >>> Robert Klemme wrote in post #1102151: >>>> On Mon, Mar 18, 2013 at 5:50 PM, Marcin Rzeźnicki > > Sorry for late answer, busy day at work. No problem at all. After all, you were the one looking for a solution. :-) >> 1. Could be viewed as an aesthetic issue: the behavior of code would >> change after the definition of the method. > > Yes, well, it's not that I want whole Ruby to be changed because of my > whining. I guess what I was trying to ask was if there is some obvious > solution that I overlooked (you know, kind of: "Geez, yet another rookie > having problems with 'super'. Listen man, do yourself a favor and do it > like this and this, it's been solved 1000 times before") I'm not aware of any right now. >> 2. As a consequence of changed behavior there would be a runtime >> effect: while executing the interpreter would have to have knowledge >> of the name used to invoke a method. Now you could argue that this >> knowledge does actually exist (caller provides it) but it may incur a >> runtime hit - for just a small amount of use cases. > > I don't think so, I mean, obviously super already has to know the name > of the method it's invoked from to do a lookup. Yes, but with the current solution the name can be baked into the compiled version of the Ruby code. While with your suggestion the name would have to be retrieved for every single call. That is the difference. > The question is - where the name is taken from. Exactly. > If method used its "actual" name instead of > "original" name then we're fine with super itself. An additional cost I > can see lurking here is that define/alias method would need to copy a > method instance (if called with one as an argument) and change its name > to reflect aliasing, rather than just reference the original one. > Anyhow, this is purely hypothetical. Yes, that might be a solution. > BTW, having given it a little thought I think that I can easily check > for the condition of being called from super myself. What it takes is > just a flag that signals that we're recursively called and a check what > real 'self' is. I still need to flesh out the details but I guess this > might work. Good. I don't understand why "self" might change when chaining method calls on the same instance but I truest you to sort that out. :-) Kind regards robert
on 2013-03-21 11:31
Robert Klemme wrote in post #1102288: >> >> I don't think so, I mean, obviously super already has to know the name >> of the method it's invoked from to do a lookup. > > Yes, but with the current solution the name can be baked into the > compiled version of the Ruby code. While with your suggestion the > name would have to be retrieved for every single call. That is the > difference. Is there anything like compiled Ruby code? I'm curious. Could you elaborate? > >> BTW, having given it a little thought I think that I can easily check >> for the condition of being called from super myself. What it takes is >> just a flag that signals that we're recursively called and a check what >> real 'self' is. I still need to flesh out the details but I guess this >> might work. > > Good. I don't understand why "self" might change when chaining method > calls on the same instance but I truest you to sort that out. :-) > Ok, I solved this. My description was rather clumsy, but code speaks more than thousand words. So, here it is. It is not tested thoroughly though. module Chain attr_accessor :next def self.init_chain(chain) chain.each_cons(2) { |first, last| first.next = last } chain.last.next = nil chain.first end def self.included(host) host.extend(ChainClassMethods) host.instance_variable_set(:@__chain_method, nil) end module ChainClassMethods def chain_method(method_name) raise "There is another chain method already defined in this module: #{@__chain_method}" if @__chain_method @__chain_method = method_name chain if method_defined? method_name end def method_added(method_name) chain if @__chain_method == method_name and not instance_variable_get(:@__in_chain) end def inherited(subclass) subclass.instance_variable_set(:@__chain_method, @__chain_method) end private def chain new_method_name = "__#{@__chain_method}" chain_method = @__chain_method @__in_chain = true alias_method new_method_name, chain_method private new_method_name chained_method = instance_method(new_method_name) define_method(chain_method) do |*args| if instance_variable_get(:@_in_chain) chained_method.bind(self).call(*args) do |*yielded_args| begin new_args = yielded_args.empty? ? args : yielded_args self.next.send chain_method, *new_args end if self.next end else @_in_chain = true send new_method_name, *args do |*yielded_args| begin new_args = yielded_args.empty? ? args : yielded_args self.next.send chain_method, *new_args end if self.next end remove_instance_variable :@_in_chain end end remove_instance_variable :@__in_chain end end end Comments are welcome. The trick here is to store method instance in a closure: chained_method = instance_method(new_method_name) and keep a flag signalling if the call is recursive (@_in_chain). If it is not then we simply pass the message to the original method adding a block that does the chaining (call it premature optimization if you will, but I have a feeling that #send is somewhat better to use in the base case because of the cost of method binding - at least one new object needs to be created every time we're binding). This block gives user the ability to write method like this: chain_method :hello def hello p "Hello" yield end If call is recursive than it is either 'super' call or plain recursion, we don't care which one it is. In this case we're binding method reference that is accessible from closure we're in to self and call it via #call, thus bypassing polymorphic call that caused troubles in the first place. Makes sense? I'll be happy to hear your take on this. > Kind regards > > robert
on 2013-03-21 11:47
On Thu, Mar 21, 2013 at 11:31 AM, Marcin R. <lists@ruby-forum.com> wrote: > Is there anything like compiled Ruby code? I'm curious. Could you > elaborate? AFAIK the Ruby code is parsed and compiled to some internal byte code representation. For more details you would have to use your favorite search engine or look at MRI source (and JRuby source and sources of the other Rubies). > Ok, I solved this. My description was rather clumsy, but code speaks > more than thousand words. I usually prefer a written explanation of the purpose of the code. The code is the solution but the goal does not always become apparent from looking at code. > This block gives user the ability to write method like > this: > > chain_method :hello > def hello > p "Hello" > yield > end Why would I want to do that? What is that supposed to do? > If call is recursive than it is either 'super' call or plain recursion, Super call is not recursion. Recursion is when a method invokes itself. Super calls are used to augment a super class's implementation of a method with additional logic specific to the derived class. > we don't care which one it is. In this case we're binding method > reference that is accessible from closure we're in to self and call it > via #call, thus bypassing polymorphic call that caused troubles in the > first place. Makes sense? I'll be happy to hear your take on this. I still don't understand what you are up to and what your ultimate goal is. Cheers robert
on 2013-03-21 13:20
Robert Klemme wrote in post #1102581: > On Thu, Mar 21, 2013 at 11:31 AM, Marcin R. <lists@ruby-forum.com> > wrote: >> Ok, I solved this. My description was rather clumsy, but code speaks >> more than thousand words. > > I usually prefer a written explanation of the purpose of the code. > The code is the solution but the goal does not always become apparent > from looking at code. > So true, but I am hoping my explanation given below was sufficient. >> This block gives user the ability to write method like >> this: >> >> chain_method :hello >> def hello >> p "Hello" >> yield >> end > > Why would I want to do that? What is that supposed to do? > As I mentioned previously my goal is to create easy to use implementation of chain pattern. You can do it manually, calling next-in-line via some sort of reference you keep ("next" in my case), but I feel that we can do better than this. My idea is to keep boilerplate in chain module and let implementations yield (because that's quite natural way of doing things like this in Ruby - you temporarily yield control to regain it later) when they need to pass the control down the chain. >> If call is recursive than it is either 'super' call or plain recursion, > > Super call is not recursion. Recursion is when a method invokes > itself. Super calls are used to augment a super class's > implementation of a method with additional logic specific to the > derived class. > Yes, so I said - EITHER super OR recursive. >> we don't care which one it is. In this case we're binding method >> reference that is accessible from closure we're in to self and call it >> via #call, thus bypassing polymorphic call that caused troubles in the >> first place. Makes sense? I'll be happy to hear your take on this. > > I still don't understand what you are up to and what your ultimate goal > is. > Hope I've already made myself clear. Thanks for your input.
on 2013-03-21 13:25
Marcin R. wrote in post #1102590: > Robert Klemme wrote in post #1102581: > > Yes, so I said - EITHER super OR recursive. > Ooops, I didn't - I wrote "If call is recursive" where I should've written "if call is flagged as in_chain". My apologies.
on 2013-03-21 14:35
On Thu, Mar 21, 2013 at 1:20 PM, Marcin R. <lists@ruby-forum.com> wrote: > Robert Klemme wrote in post #1102581: >> On Thu, Mar 21, 2013 at 11:31 AM, Marcin R. <lists@ruby-forum.com> >> wrote: > >> I usually prefer a written explanation of the purpose of the code. >> The code is the solution but the goal does not always become apparent >> from looking at code. > > So true, but I am hoping my explanation given below was sufficient. >> Why would I want to do that? What is that supposed to do? > > As I mentioned previously my goal is to create easy to use > implementation of chain pattern. Are you talking about "chain of responsibility"? http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern http://c2.com/cgi/wiki?ChainOfResponsibilityPattern > You can do it manually, calling > next-in-line via some sort of reference you keep ("next" in my case), > but I feel that we can do better than this. My idea is to keep > boilerplate in chain module and let implementations yield (because > that's quite natural way of doing things like this in Ruby - you > temporarily yield control to regain it later) when they need to pass the > control down the chain. I don't see the advantage of using "yield" over any other method name. It seems you are going through hoops just to allow yield to delegate to another instance. I don't think it's worthwhile. For me this would be good enough: ChainedBase = Struct.new :delegate do def chain(*a, &b) m = caller[0][%r{`(.*?)'\z}, 1] d = delegate and d.send(m, *a, &b) end end class Der < ChainedBase def foo x puts "entry Der" chain x puts "exit Der" end end class Catcher def method_missing(m, *a, &b) printf "Caught: %s(%s)\n", m, a.map(&:inspect).join(', ') end end o = Der.new o.delegate = Catcher.new o.foo 123 >>> If call is recursive than it is either 'super' call or plain recursion, >> >> Super call is not recursion. Recursion is when a method invokes >> itself. Super calls are used to augment a super class's >> implementation of a method with additional logic specific to the >> derived class. > > Yes, so I said - EITHER super OR recursive. Yeah, but the fact that you mix them together is suspicious. Chain of responsibility is all about (conditional) delegation to another instance. Calling superclass methods does not fit that pattern because it is the *same* instance. > Hope I've already made myself clear. It seems so. > Thanks for your input. You're welcome robert
on 2013-03-21 15:16
On Thu, Mar 21, 2013 at 2:34 PM, Robert Klemme
<shortcutter@googlemail.com> wrote:
PS: Another way to implement this would be completely external:
chain = [ ... ]
chain.find do |obj|
obj.handle 123
end
With the convention that a successful handling leads to true return, for
example
class Handler
def handle(x)
wants_to_handle(x).tap do |yes|
yes or return false
...
end
end
end
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.