[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Re: before, after and around Ruby 1.9

Yukihiro Matsumoto

9/5/2007 11:32:00 PM

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.

22 Answers

Lionel Bouton

9/5/2007 11:42:00 PM

0

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

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

Lionel

Yossef Mendelssohn

9/6/2007 12:41:00 AM

0

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.

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.

--
-yossef


Joel VanderWerf

9/6/2007 1:32:00 AM

0

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

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

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

Morton Goldberg

9/6/2007 4:13:00 AM

0

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.

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

Daniel DeLorme

9/6/2007 5:18:00 AM

0

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

Trans

9/6/2007 3:52:00 PM

0



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.

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

T.


benjohn

9/6/2007 5:52:00 PM

0


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.

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

Cheers,
Benjohn




Rick DeNatale

9/7/2007 12:25:00 AM

0

On 9/6/07, Benjohn Barnes <benjohn@fysh.org> wrote:
>
> 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.
>
> :-) 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.

Yes, I totally agree, this is a real problem. I fear that things will
all get wrapped up around the axle because it depends on the temporal
ordering of execution. How do you control this when different parts
of the overall program are dynamically requiring extensions and
redefinitions? Imagine how this would or wouldn't work in a Rails app
using multiple plugins.
>
> 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.

On the surface, I'm not sure that any of the proposed solutions are
any better than what we have now with alias_method and explicit
invocation of the replaced method implementation.

The proposals might make it seem more transparent, but in my
experience, transparency isn't always a good thing when it hides
issues that need to be dealt with. I've got some experience with
that, since I take the blame for producing a transparent distributed
implementation of Smalltalk which caused all kinds of headaches
because of such hidden issues.


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

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

Brian Mitchell

9/7/2007 1:19:00 PM

0

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.

Rick DeNatale

9/7/2007 10:15:00 PM

0

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

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

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...