Before, after and around Ruby 1.9

On 9/6/07, Benjohn B. [email protected] wrote:

def foo; super; end # REDEFINE
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.

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


Rick DeNatale

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

On Sep 6, 2007, at 10:35 AM, Yukihiro M. wrote:

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

I believe Perl 6 takes a similar approach using the next keyword/
method/whatever (I don’t know how it is implemented).

James Edward G. II

On 9/6/07, Trans [email protected] wrote:

In message “Re: before, after and around Ruby 1.9”
|end

skipping over around advice.
Maybe I read you wrong, there should be a way for metaprogrammers to
know where a method was defined, but that should not be the only way
to access to super or redefined methods.
Actually
super_at(object/module/class)
might be a useful tool, but I would like to have
super
and
old (or next or whateverMatzLikes :wink:
to just step up one level for “non meta use”
part of it’s charm and power.
Hmm yes sure, but when Matz answered your question about
after/before/around
it seemed to me that it would be a special case built in instead a more
general.
I might be completely wrong of course :wink:

I’d prefer having an easy way to do this with metaprogramming and then
put the implementations into the library.

There are two problems with that. 1) any implementation one can create
as an add-on is going to have a less intuitive interface,
true but it would still be an interface decided upon early
and 2) but
much more importantly, any such implementation is going to be severely
hampered in execution efficiency compared to a native capability.
Losing you here, do you mean that special bytecode would be emitted
for these three constructs that would be more efficent than for method
redefinition?
Interesting topic but I am too ignorant of it :frowning:
Cheers
Robert

On Fri, 07 Sep 2007 00:57:09 +0900, Trans wrote:

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.

shouldn’t this work as expected in this case?

Foo = Class.new(Foo) { def foo; super; end} #AROUND

Actually, it already does, but raises a warning :slight_smile:

On Sep 7, 1:30 am, “Robert D.” [email protected] wrote:

Hi,
|class Foo < Foo # ← This is a type error in 1.8
for the new. I don’t whatever name is suitable for new super yet,

and
old (or next or whateverMatzLikes :wink:
to just step up one level for “non meta use”

Yes, that’s a common consideration, but there’s a subtle reason why
you don’t really want that, which is what I’m trying to get across.

When someone writes an around advice, they expect it be wrap the the
method being advised. If the programmer of the original method used
#super rather than #old, it wouldn’t work. And that’s bad. Because the
point is, I should be able to advise any method I want.

Of course, there may arise special cases where skipping advice in
needed, but that is a rare/dangerous meta-coding need, in which case
#super_at should be more than enough to satisfy the requirement, eg.
super_at(self.class) for instance.

much more importantly, any such implementation is going to be severely
hampered in execution efficiency compared to a native capability.

Losing you here, do you mean that special bytecode would be emitted
for these three constructs that would be more efficent than for method
redefinition?

No no. I simply mean that any lib built-on top of Ruby’s current meta-
programming constructs is going to be much slower (and less robust)
than something built-in to Ruby itself, basically because method
composition operates at such a low-level.

T.

On 9/5/07, Yukihiro M. [email protected] wrote:

explicitly removed, i.e.
end
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 '”
– 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.

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

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


Rick DeNatale

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

On 9/7/07, Trans [email protected] wrote:

than something built-in to Ruby itself, basically because method
composition operates at such a low-level.

Hmm maybe yes, the VM could simply recompile the method, OTOH if Ruby2
will give us the ability we could recompile the method ourselves as we
could e.g. in Squeak.

Cheers
Robert

On 9/8/07, Christian N. [email protected] wrote:

Yukihiro M. [email protected] 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:

Yukihiro M. [email protected] 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”?

On Sat, 08 Sep 2007 20:00:50 +0200, gabriele renzi wrote:

D.new.foo #=> hello world
^^^^^
C.new

On Sun, 09 Sep 2007 02:53:13 +0900, Logan C. wrote:

On 9/8/07, Christian N. [email protected] wrote:

Yukihiro M. [email protected] 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:

AFAIK “inner” in simula is similar to “inner” in Beta, and is some kind
of
dual to super.
i.e.

class C
def foo
print ‘hello’
inner
end
end

class D < C
def foo
print ’ world’
end
end
D.new.foo #=> hello world

On 9/8/07, gabriele renzi [email protected] wrote:

On Sat, 08 Sep 2007 20:00:50 +0200, gabriele renzi wrote:

D.new.foo #=> hello world
^^^^^
C.new

Interesting, but what if C has more than one subclass?

2007/9/8, Rick DeNatale [email protected]:

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. (…)

Rick, I don’t see a way to solve this problem. If you add around
methods to an existing method, the order in which you do this is
always important, regardless of the syntax or the implementation,
isn’t it?

Regards,
Pit

2007/9/11, Rick DeNatale [email protected]:

Yes, my concern is that because there’s been so much clever use of
dynamic loading of code in Ruby, and particularly in Rails, that such
constructs might lead to bedlam.

This is a valid concern. Integrating AOP in the language can be seen
as one more way to shoot you in the foot. But many of us (I’m sure you
too) like Ruby for the possibilities it gives to the developer, and I
would be very happy to get a standard and efficient AOP
implementation.

And at least I think that it’s an argument for not repurposing super
which has a meaning which depends strictly on the class
inheritance/module inclusion chain and not on how that chain happened
to get to a particular state.

The module inclusion chain is already dependent on the sequence in
which the code is executed, especially if you use super. What I like
about Matz’s proposal is that there is still only one of these chains
and one way to walk them: super. Having a different way to call the
next method would make things more complicated, IMO. Anyway, I’m
looking forward to 2.0.

Regards,
Pit

On 9/10/07, Pit C. [email protected] wrote:

isn’t it?
Yes, my concern is that because there’s been so much clever use of
dynamic loading of code in Ruby, and particularly in Rails, that such
constructs might lead to bedlam.

And at least I think that it’s an argument for not repurposing super
which has a meaning which depends strictly on the class
inheritance/module inclusion chain and not on how that chain happened
to get to a particular state.


Rick DeNatale

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

Rick DeNatale wrote:

class A

And now a word from my superclass!
Hi from A

Actually it will only produce “And now a word from my superclass!” I
assume you just forgot the super in there.

whereas Matz’ proposal would produce:

And now a wird from my superclass!
And now a word from my superclass!
Hi from A

My understanding of Matz’ proposal is that you would need to specify
“class B” to get that result. By specifying “class B < A” you would get
the same result as current ruby.

How do you REPLACE a method implementation which invokes super in this
brave new world?

By defining the method in “regular mode” as opposed to “re-open mode”
regular: class A < Object
re-open: class A
At the risk of repeating myself I really prefer the previous suggestion:
regular: class A
re-open: class A < A

Daniel

On Sep 11, 7:39 pm, “Rick DeNatale” [email protected] wrote:

class A

brave new world?
A possibility:

class B
redef m
puts “And now a word from my superclass!”
super
end
end

T.

On 9/11/07, Pit C. [email protected] wrote:

The module inclusion chain is already dependent on the sequence in
which the code is executed, especially if you use super. What I like
about Matz’s proposal is that there is still only one of these chains
and one way to walk them: super. Having a different way to call the
next method would make things more complicated, IMO.

Here’s the sequence dependency I’m concerned about.

class A
def m
puts “Hi from A”
end
end

class B < A
def m
puts “And now a wird from my superclass.!”
super
end

class B < A
#fix the typo
def m
puts “And now a word from my superclass!”
end
end

B.new.m

With the current semantics, this will produce:

And now a word from my superclass!
Hi from A

whereas Matz’ proposal would produce:

And now a wird from my superclass!
And now a word from my superclass!
Hi from A

How do you REPLACE a method implementation which invokes super in this
brave new world?

Rick DeNatale

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

On 9/11/07, Rick DeNatale [email protected] wrote:

class A

brave new world?
Easy:

class B
remove_method :m
def m … end
end

Seems to work well with the idea that the method is being removed
making hard to accidentally cover code up.

The main point to make here is that the property of composability is
still a little weird. This makes loading multiple definitions very
fragile since order could easily matter on which things wrap and which
things don’t.

Consider the original syntax from Matz’s slides (RubyConf '03):

class Foo
def foo:pre(*args)
p ‘pre’
end
def foo:post(*args)
p ‘post’
end
def foo:wrap(*args)
p ‘wrap pre’
super
p ‘wrap post’
end
def foo
p ‘foo’
end
end

Foo.new.foo

Output:
wrap pre
pre
foo
post
wrap post

Notice how the pre/post/wrap is syntax defined before and
independently of the foo method. This is a boost to composability
because it waits for foo to exist and if foo is replaced it is still
acting on the correct foo.

This is a big win for dynamic software… the kind written using Ruby.
I still like the super call, and with this syntax, it is easy to keep
it clear on how recursive calls will act. If we aren’t careful and
these extensions become widespread, I have a feeling that a lot of
code will find itself breaking other code too easily.

The other side effect of this syntax is that is provides an easy way
to extend into other method specialization features without having
uncooperative and short term syntaxes.

Brian.