before, after and around Ruby 1.9

T

Trans

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

T.
 
Y

Yossef Mendelssohn

Hi,

In message "Re: before, after and around Ruby 1.9"

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

Trans

Hi,

In message "Re: before, after and around Ruby 1.9"

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

Brian Mitchell

------=_Part_119_30028435.1189171096607
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Hi,

In message "Re: before, after and around Ruby 1.9"

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

------=_Part_119_30028435.1189171096607
Content-Type: text/x-ruby-script; name="stacked_methods.rb"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="stacked_methods.rb"
X-Attachment-Id: f_f6appnjz

IyBBIHNpbXBsZSBpbXBsZW1lbnRhdGlvbiBvZiBtZXRob2Qgc3RhY2tpbmcgaW4gcHVyZSBSdWJ5
Cgptb2R1bGUgU3RhY2tlZE1ldGhvZHMKICBkZWYgc2VsZi5pbmNsdWRlZChtb2QpCiAgICBtb2Qu
aW5zdGFuY2VfZXZhbCBkbwogICAgICBAc3RhY2sgPSBNZXRob2RTdGFjay5uZXcKICAgICAgQHN0
YWNrLm1ldGhvZHMgPSBIYXNoLm5ldyB7fGgsa3wgaFtrXSA9IFtdfQogICAgICBAc3RhY2suY2Fs
bGVycyA9IEhhc2gubmV3IHt8aCxrfCBoW2tdID0gSGFzaC5uZXcoLTEpfQogICAgICBAdHJhbXBv
bGluZSA9IE1vZHVsZS5uZXcKICAgICAgaW5jbHVkZSBAdHJhbXBvbGluZQogICAgZW5kCiAgICBt
b2QuZXh0ZW5kIEhvb2tzCiAgZW5kCiAgCiAgTWV0aG9kU3RhY2sgPSBTdHJ1Y3QubmV3KDptZXRo
b2RzLCA6Y2FsbGVycykKICAKICBtb2R1bGUgSG9va3MKICAgIGRlZiBtZXRob2RfYWRkZWQobmFt
ZSkKICAgICAgc3RhY2sgPSBAc3RhY2sgIyBVc2luZyB0aGlzIGluIGRlZmluZV9tZXRob2QncyBj
bG9zdXJlLgogICAgICBzdGFjay5tZXRob2RzW25hbWVdIDw8IGluc3RhbmNlX21ldGhvZChuYW1l
KQogICAgICB1bmxlc3MgQHRyYW1wb2xpbmUuaW5zdGFuY2VfbWV0aG9kcy5pbmNsdWRlPyBuYW1l
CiAgICAgICAgQHRyYW1wb2xpbmUuY2xhc3NfZXZhbCBkbwogICAgICAgICAgZGVmaW5lX21ldGhv
ZCBuYW1lIGRvIHwqYSwgJmJ8CiAgICAgICAgICAgIGlkeCA9IHN0YWNrLmNhbGxlcnNbVGhyZWFk
LmN1cnJlbnRdW25hbWVdIC09IDEKICAgICAgICAgICAgYmVnaW4KICAgICAgICAgICAgICBuZXh0
X2NhbGwgPSBzdGFjay5tZXRob2RzW25hbWVdW2lkeF0KICAgICAgICAgICAgICBpZiBuZXh0X2Nh
bGwKICAgICAgICAgICAgICAgIG5leHRfY2FsbC5iaW5kKHNlbGYpLmNhbGwoKmEsICZiKQogICAg
ICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIHN1cGVyKCphLCAmYikKICAgICAgICAgICAg
ICBlbmQKICAgICAgICAgICAgZW5zdXJlCiAgICAgICAgICAgICAgc3RhY2suY2FsbGVyc1tUaHJl
YWQuY3VycmVudF1bbmFtZV0gKz0gMQogICAgICAgICAgICBlbmQKICAgICAgICAgIGVuZAogICAg
ICAgIGVuZAogICAgICBlbmQKICAgIGVuZAogICAgCiAgICBkZWYgbWV0aG9kX3JlbW92ZWQobmFt
ZSkKICAgICAgQHN0YWNrLm1ldGhvZHNbbmFtZV0gPSBbXQogICAgICBAdHJhbXBvbGluZS5jbGFz
c19ldmFsIHtyZW1vdmVfbWV0aG9kIG5hbWV9CiAgICBlbmQKICBlbmQKZW5kCgpjbGFzcyBFeGFt
cGxlCiAgaW5jbHVkZSBTdGFja2VkTWV0aG9kcwogIAogIGRlZiBmaWIobikKICAgIGlmIG4gPiAx
CiAgICAgIGZpYihuIC0gMikgKyBmaWIobiAtIDEpCiAgICBlbHNlCiAgICAgIDEKICAgIGVuZAog
IGVuZAoKICBkZWYgZmliKG4sIHByZWZpeCA9ICdDYWxjdWxhdGluZyBmaWIgb2YgJykKICAgIHB1
dHMgcHJlZml4ICsgbi50b19zCiAgICBzdXBlcihuKQogIGVuZAplbmQKCkV4YW1wbGUubmV3LmZp
YigyKQo=
------=_Part_119_30028435.1189171096607--
 
R

Rick DeNatale

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

Pit Capitain

2007/9/8 said:
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
 
R

Rick DeNatale

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?

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

Pit Capitain

2007/9/11 said:
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
 
R

Rick DeNatale

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

Daniel DeLorme

Rick said:
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

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
 
T

Trans

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?

A possibility:

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

T.
 
B

Brian Mitchell

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?

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top