Brian Mitchell
9/7/2007 1:19:00 PM
On 9/5/07, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:
> Hi,
>
> In message "Re: before, after and around Ruby 1.9"
> on Thu, 6 Sep 2007 03:07:14 +0900, Trans <transfire@gmail.com> writes:
>
> |Any chance Ruby 1.9 will have before, after and around method
> |composition support?
>
> No. Wait for 2.0 for built-in method combination. The vague plan is
> making open-class to stack methods on the current ones, unless
> explicitly removed, i.e.
>
> class Foo < Object
> def foo
> puts "Foo#foo (1)"
> end
> end
> class Foo # re-open
> def foo
> super # calls the first foo
> puts "Foo#foo (2)"
> end
> end
>
> will print
>
> Foo#foo (1)
> Foo#foo (2)
>
> No alias required.
>
> This works as "around". And "before" and "after" can be rewritten
> using "around". Note that this is not a fixed idea at all.
>
> matz.
I started implementing a simple emulator for this super behavior. It
seems to work fine for your example:
module StackedMethods
def self.included(mod)
mod.instance_eval do
@stack = MethodStack.new
@stack.methods = Hash.new {|h,k| h[k] = []}
@stack.callers = Hash.new {|h,k| h[k] = Hash.new(-1)}
@trampoline = Module.new
include @trampoline
end
mod.extend Hooks
end
MethodStack = Struct.new(:methods, :callers)
module Hooks
def method_added(name)
stack = @stack # Using this in define_method's closure.
stack.methods[name] << instance_method(name)
unless @trampoline.instance_methods.include? name
@trampoline.class_eval do
define_method name do |*a, &b|
idx = stack.callers[Thread.current][name] -= 1
begin
next_call = stack.methods[name][idx]
if next_call
next_call.bind(self).call(*a, &b)
else
super(*a, &b)
end
ensure
stack.callers[Thread.current][name] += 1
end
end
end
end
end
def method_removed(name)
@stack.methods[name] = []
@trampoline.class_eval {remove_method name}
end
end
end
Just include the StackedMethods module before the method definitions to use it.
The odd thing is I get a crash on my fib example:
class Example
include StackedMethods
def fib(n)
if n > 1
fib(n - 2) + fib(n - 1)
else
1
end
end
def fib(n, prefix = 'Calculating fib of ')
puts prefix + n.to_s
super(n)
end
end
Output:
$ ruby19 stacked_methods.rb
Calculating fib of 2
Calculating fib of 0
stacked_methods.rb:33: -- control frame ----------
c:7085 p:0023 s:31872 b:31870 l:00139c d:001869 BLOCK stacked_methods.rb:33
c:7084 p:0141 s:31868 b:31867 l:00139c d:001866 LAMBDA stacked_methods.rb:33
c:7083 p:---- s:31864 b:31862 l:001861 d:001861 FINISH :methods
c:7082 p:0102 s:31860 b:31858 l:00139c d:001857 LAMBDA stacked_methods.rb:30
c:7081 p:---- s:31855 b:31853 l:001852 d:001852 FINISH :methods
c:7080 p:0102 s:31851 b:31849 l:00139c d:001848 LAMBDA stacked_methods.rb:30
c:7079 p:---- s:31846 b:31844 l:001843 d:001843 FINISH :methods
c:7078 p:0102 s:31842 b:31840 l:00139c d:001839 LAMBDA stacked_methods.rb:30
... snip ...
c:0014 p:0102 s:0052 b:0050 l:00118c d:000049 LAMBDA stacked_methods.rb:30
c:0013 p:---- s:0047 b:0045 l:000044 d:000044 FINISH :methods
c:0012 p:0102 s:0043 b:0041 l:00118c d:000040 LAMBDA stacked_methods.rb:30
c:0011 p:---- s:0038 b:0036 l:000035 d:000035 FINISH :[]=
c:0010 p:0008 s:0034 b:0032 l:000031 d:000031 METHOD stacked_methods.rb:59
c:0009 p:0019 s:0028 b:0028 l:000027 d:000027 METHOD stacked_methods.rb:52
c:0008 p:---- s:0024 b:0024 l:000023 d:000023 FINISH :yield
c:0007 p:---- s:0022 b:0022 l:000021 d:000021 CFUNC :call
c:0006 p:0088 s:0018 b:0018 l:00118c d:000017 LAMBDA stacked_methods.rb:28
c:0005 p:---- s:0015 b:0013 l:000012 d:000012 FINISH :initialize
c:0004 p:0008 s:0011 b:0009 l:000008 d:000008 METHOD stacked_methods.rb:59
c:0003 p:0035 s:0005 b:0005 l:000004 d:000004 TOP stacked_methods.rb:63
c:0002 p:---- s:0003 b:0003 l:000002 d:000002 FINISH :inherited
c:0001 p:---- s:0001 b:-001 l:000000 d:000000 ------
---------------------------
DBG> : "stacked_methods.rb:30:in `block (2 levels) in method_added'"
DBG> : "stacked_methods.rb:30:in `block (2 levels) in method_added'"
... snip ...
DBG> : "stacked_methods.rb:30:in `block (2 levels) in method_added'"
DBG> : "stacked_methods.rb:30:in `block (2 levels) in method_added'"
DBG> : "stacked_methods.rb:59:in `fib'"
DBG> : "stacked_methods.rb:52:in `fib'"
DBG> : "stacked_methods.rb:28:in `call'"
DBG> : "stacked_methods.rb:28:in `block (2 levels) in method_added'"
DBG> : "stacked_methods.rb:59:in `fib'"
DBG> : "stacked_methods.rb:63:in `<main>'"
-- backtrace of native function call (Use addr2line) --
-------------------------------------------------------
[BUG] Segmentation fault
ruby 1.9.0 (2007-09-07) [i686-darwin9.0.0b5]
Abort trap
It seems it hits a nasty bug somewhere in the VM on this machine. It
seems to have an issue with recursive calling on the line with
super(*a, &b). It is possibly related to my use of recursion in fib
here. I've attached my original source file.
Brian.