Forum: Ruby Finding one's way with 'super' in define_method/alias_emthod

194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 "Marcin Rzeźnicki" <marcin.rzeznicki@gmail.com> (Guest)
on 2013-03-18 17:53
(Received via mailing list)
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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-18 19:16
(Received via mailing list)
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
194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 Marcin Rzeźnicki (marcin_r)
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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-18 22:39
(Received via mailing list)
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
194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 Marcin Rzeźnicki (marcin_r)
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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-19 15:33
(Received via mailing list)
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
194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 Marcin Rzeźnicki (marcin_r)
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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-21 11:47
(Received via mailing list)
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
194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 Marcin Rzeźnicki (marcin_r)
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.
194370d5c541dd34c474b1fa656265d4?d=identicon&s=25 Marcin Rzeźnicki (marcin_r)
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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-21 14:35
(Received via mailing list)
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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-03-21 15:16
(Received via mailing list)
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

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.