Before, after and around Ruby 1.9

Any chance Ruby 1.9 will have before, after and around method
composition support?

T.

Hi,

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.

···

In message "Re: before, after and around Ruby 1.9" on Thu, 6 Sep 2007 03:07:14 +0900, Trans <transfire@gmail.com> writes:

Yukihiro Matsumoto wrote:

Hi,

>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.
  
Does it mean that we won't be able to call the ancestor's implementation
when monkey-patching a method?

I'm asking because I do this very (ugly?) thing for the Rails 1.1
PostgreSQL ActiveRecord driver: add_column is buggy with my PostgreSQL
version and it happens that the AbstractDriver implementation works out
of the box :slight_smile:

Lionel

···

In message "Re: before, after and around Ruby 1.9" > on Thu, 6 Sep 2007 03:07:14 +0900, Trans <transfire@gmail.com> writes:

Very interesting. Using 'super' in this way lets you easily have
"around", and as you say, "before" and "after" can be written using
"around" (such as your "after" example.) I like the idea, though it
does confuse/overload 'super' a bit

class Foo
  def foo
    'foo'
  end
end

class Bar < Foo
  def foo
    super + 'bar' # results in 'foobar'
  end
end

class Bar < Foo
  def foo
    super + 'baz' # results in 'foobarbaz'
  end
end

One thing I'm not sure about is what happens with the original method
if the class is re-opened and the new version doesn't use 'super' at
all. I guess it sticks around without anything referring to it. Or
there could be an optimization to actually replace the old method at
that point. The other is if the new version takes different arguments

class Foo
  def foo
    'foo'
  end
end

class Foo
  def foo(caps = true)
    if caps
      super.upcase
    else
      super
    end
  end
end

I'm imagining some aliasing happens behind the scenes, so the new
Foo#foo calls some Foo#old_foo and everything works out without
errors.

As you said, it's not a fixed idea at all. I was just running through
some of the implications.

···

On Sep 5, 6:32 pm, Yukihiro Matsumoto <m...@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 <transf...@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.

--
-yossef

Yukihiro Matsumoto wrote:

Hi,

>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

What if you want to reopen _without_ the "around" semantics? Could we have these two variations:

class Foo < Foo # <-- This is a type error in 1.8
   def foo; super; end # AROUND
end

class Foo
   def foo; super; end # REDEFINE
end

At least that is a conservative extension.

Or perhaps some new syntax for the AROUND case...?

···

In message "Re: before, after and around Ruby 1.9" > on Thu, 6 Sep 2007 03:07:14 +0900, Trans <transfire@gmail.com> writes:

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Perhaps to avoid overloading 'super', you might use 'previous' or 'existing' since we would be calling a previously existing version of the method.

class Foo < Object
   def foo
     puts "Foo#foo (1)"
   end
end
class Foo # re-open
   def foo
     previous
     puts "Foo#foo (2)"
   end
end

lass Foo < Object
   def foo
     puts "Foo#foo (1)"
   end
end
class Foo # re-open
   def foo
     existing
     puts "Foo#foo (2)"
   end
end

Just a suggestion.

Regards, Morton

···

On Sep 5, 2007, at 7:32 PM, Yukihiro Matsumoto 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.

After my very own heart! I think that's a great approach. And I will
try to be patient for 2.0 :wink:

T.

···

On Sep 5, 4:32 pm, Yukihiro Matsumoto <m...@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 <transf...@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.

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

stacked_methods.rb (1.45 KB)

···

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.

---------------------------

: "stacked_methods.rb:30:in `block (2 levels) in method_added'"
: "stacked_methods.rb:30:in `block (2 levels) in method_added'"

... snip ...

: "stacked_methods.rb:30:in `block (2 levels) in method_added'"
: "stacked_methods.rb:30:in `block (2 levels) in method_added'"
: "stacked_methods.rb:59:in `fib'"
: "stacked_methods.rb:52:in `fib'"
: "stacked_methods.rb:28:in `call'"
: "stacked_methods.rb:28:in `block (2 levels) in method_added'"
: "stacked_methods.rb:59:in `fib'"
: "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.

Yukihiro Matsumoto <matz@ruby-lang.org> writes:

This works as "around". And "before" and "after" can be rewritten
using "around". Note that this is not a fixed idea at all.

BTW, do you know how Simula uses "inner"?

···

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

Hi,

Does it mean that we won't be able to call the ancestor's implementation
when monkey-patching a method?

I am not sure what you mean by monkey-patching. At least you can call
the ancestor's implementation, when

  * it's not covered by the class
  * or the method is explicitly removed

I'm asking because I do this very (ugly?) thing for the Rails 1.1
PostgreSQL ActiveRecord driver: add_column is buggy with my PostgreSQL
version and it happens that the AbstractDriver implementation works out
of the box :slight_smile:

In this case, you just need to remove the covering method, I guess.

              matz.

···

In message "Re: before, after and around Ruby 1.9" on Thu, 6 Sep 2007 08:41:45 +0900, Lionel Bouton <lionel-subscription@bouton.name> writes:

Hi,

What if you want to reopen _without_ the "around" semantics?

I thought we need to remove the previous one first.

Could we have these two variations:

class Foo < Foo # <-- This is a type error in 1.8
  def foo; super; end # AROUND
end

class Foo
  def foo; super; end # REDEFINE
end

At least that is a conservative extension.

Or maybe providing two supers, one for the current behavior, the other
for the new. I don't whatever name is suitable for new super yet,
however.

              matz.

···

In message "Re: before, after and around Ruby 1.9" on Thu, 6 Sep 2007 10:32:03 +0900, Joel VanderWerf <vjoel@path.berkeley.edu> writes:

Joel VanderWerf wrote:

What if you want to reopen _without_ the "around" semantics? Could we have these two variations:

class Foo < Foo # <-- This is a type error in 1.8
  def foo; super; end # AROUND
end

class Foo
  def foo; super; end # REDEFINE
end

+1

This is brilliant. It's simple, and it perfectly expresses the idea of reopening the class to add functionality on top of the existing methods. Instead of subclassing a parent class, Foo subclasses itself (sort of like Foo=Class.new(Foo)). Just Brilliant.

Daniel

Hi,

···

In message "Re: before, after and around Ruby 1.9" on Thu, 6 Sep 2007 13:12:53 +0900, Morton Goldberg <m_goldberg@ameritech.net> writes:

Perhaps to avoid overloading 'super', you might use 'previous' or
'existing' since we would be calling a previously existing version of
the method.

Interesting idea, especially knowing Common Lisp Object System uses
call-next-method instead of super.

              matz.

Yukihiro Matsumoto wrote:
> Hi,

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

What if you want to reopen _without_ the "around" semantics? Could we
have these two variations:

class Foo < Foo # <-- This is a type error in 1.8
   def foo; super; end # AROUND
end

Almost an anonymous cut.

class Foo
   def foo; super; end # REDEFINE
end

At least that is a conservative extension.

The downside here is it's static syntax --making it harder to work
with in dynamic metacode.

T.

···

On Sep 5, 6:32 pm, Joel VanderWerf <vj...@path.berkeley.edu> wrote:

> In message "Re: before, after and around Ruby 1.9" > > on Thu, 6 Sep 2007 03:07:14 +0900, Trans <transf...@gmail.com> writes:

Just to try to crystalize my own thoughts about this in general. I'm
concerned that because of the dynamic nature of "assembling" the
pieces of a Ruby program, some of these proposals might lead to
indeterminate (or at best mysterious) results because of the
difficulties in figuring out the order of code parsing/execution. For
example, Rails loves to automatically load code 'on demand' using
Module#constant_missing this leads to subtleties in 'sophisticated'
Rails coding such as using

   Foo.class_eval do
       #class modifications here
   end

instead of the more usual

  class Foo
     #class modifications here
  end

In the case where the Foo being 'opened' doesn't actually exist yet,
the more normal code will create it, and the modifications will at
best be overwritten when the 'real' class definition is encountered,
and at worst the 'real' class definition won't be loaded at all
leaving a class with rather anemic capabilities.

In Rails, due to its use of constant_missing, the first form will
actully load the original class and then modify it. Not that this is
necessarily, and it probably isn't without thinking about it too much,
a good thing to add to the base language.

In a related vein, Charlie Savage recently wrote a thoughtful analysis
of one of the ways which Rails uses alias_method to do 'poor mans'
aspect oriented programming to implement the GOF decorator pattern

http://cfis.savagexi.com/articles/2007/09/05/rails-unusual-architecture

He makes some points about why this might not be the best approach.

···

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Yukihiro Matsumoto <matz@ruby-lang.org> writes:

> This works as "around". And "before" and "after" can be rewritten
> using "around". Note that this is not a fixed idea at all.

BTW, do you know how Simula uses "inner"?

He might, but I don't and couldn't find it by googling, so I'd love
some elaboration :slight_smile:

···

On 9/8/07, Christian Neukirchen <chneukirchen@gmail.com> wrote:

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

Yukihiro Matsumoto wrote:

>I'm asking because I do this very (ugly?) thing for the Rails 1.1
>PostgreSQL ActiveRecord driver: add_column is buggy with my PostgreSQL
>version and it happens that the AbstractDriver implementation works out
>of the box :slight_smile:

In this case, you just need to remove the covering method, I guess.
  
So obvious I didn't saw it :slight_smile: Thanks.

Hi Matz,

super for this new usage just doesn't feel right to me.

Isn't this really looking for syntactic sugar for something like:

class Foo #reopen

  alias_method :old_foo, :foo

  def foo
     old_foo #around
  end
end

So as to avoid needing to explicitly alias the method and come up with a name?

Perhaps instead of super in this case something in the vein of old,
or previous.

Alternatively what about instead of def

class Foo
   around foo
        #do something
        foo
       # do something else
  end
end

Which I would see as internally aliasing the existing foo, and
resolving reference to fo in the new methods body to the alias.

Or instead of around, how about wrap, with a corresponding unwrap to
back out the wrapper method?

···

On 9/5/07, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

>Could we have these two variations:
>
>class Foo < Foo # <-- This is a type error in 1.8
> def foo; super; end # AROUND
>end
>
>class Foo
> def foo; super; end # REDEFINE
>end
>
>At least that is a conservative extension.

Or maybe providing two supers, one for the current behavior, the other
for the new. I don't whatever name is suitable for new super yet,
however

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

How would you be sure (and why would you want to?) know when a
"previous" exists or not? It rarely make sense to avoid advice.
However, when absolutely needed one can fallback to directed calling
with things like:

  super_at(Object)

or

  as(Object).foo

T.

···

On Sep 5, 6:46 pm, Yukihiro Matsumoto <m...@ruby-lang.org> wrote:

Hi,

In message "Re: before, after and around Ruby 1.9" > on Thu, 6 Sep 2007 10:32:03 +0900, Joel VanderWerf <vj...@path.berkeley.edu> writes:

>What if you want to reopen _without_ the "around" semantics?

I thought we need to remove the previous one first.

>Could we have these two variations:
>
>class Foo < Foo # <-- This is a type error in 1.8
> def foo; super; end # AROUND
>end
>
>class Foo
> def foo; super; end # REDEFINE
>end
>
>At least that is a conservative extension.

Or maybe providing two supers, one for the current behavior, the other
for the new. I don't whatever name is suitable for new super yet,
however.

:slight_smile: I think I'm probably missing something, but I'm not sure that I'm keen on this approach though I find the simplicity alluring.

The issue I have is what happens when the class is re-opened more than once: when lots of methods get wrapped around? As far as I can see, the order of the openings in the code is important, and I don't like the feel of that at all. I feel like I ought to be able to reorder things such as the order of class definition without that having a semantic effect. In fact, I think I find it confusing already that if I define the same method more than once for the same class, then the last definition will be the one that's used.

Am I missing something? Does anyone else feel similarly? Is the consensus that this wouldn't be an issue in practice?

I suppose I'd prefer something that would somewhat break away from what happens now...

I'd have all of the implementations of a method (within the class) be called. I'd then have another mechanism for enforcing ordering: a syntax that lets an implementation insist that it requires another to be run before it (or after it). Once you've insisted that another implementation runs before you, then you're able to utilise the return state of that method you asked to run before you.

Then again, what I'm asking for could easily be done with a library, so perhaps it's not so important :slight_smile:

Cheers,
  Benjohn

···

On 6 Sep 2007, at 06:17, Daniel DeLorme wrote:

Joel VanderWerf wrote:

What if you want to reopen _without_ the "around" semantics? Could we have these two variations:
class Foo < Foo # <-- This is a type error in 1.8
  def foo; super; end # AROUND
end
class Foo
  def foo; super; end # REDEFINE
end

+1

This is brilliant. It's simple, and it perfectly expresses the idea of reopening the class to add functionality on top of the existing methods. Instead of subclassing a parent class, Foo subclasses itself (sort of like Foo=Class.new(Foo)). Just Brilliant.