[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Meta-Meta-Programming, revisited

Erik Veenstra

7/21/2006 3:49:00 PM

Do you remember the discussion about monitor-functions and
metameta-programming?

Well, I've completely rewritten this Module#wrap_method. It
should be more robust now: thread-safety, better execution
order of recursively wrapped methods, better execution order of
method-is-defined-in-superclass, etc.

I added some convenience methods as well: Module#pre_condition
and Module#post_condition. These are really easy to use!

The article and the code are here:

http://www.erikve...monitorfunctions/...

Please, read it, both the article and the implementation,
especially the implementation, read it again, think about it,
test it, analyze it, use it and shoot.

But do not benchmark it... ;]

Thanks.

gegroet,
Erik V. - http://www.erikve...

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

EXCERPT:

I had a discussion with a friend. A Java guy. He wants the
arguments of a method call being checked. "I want the first one
to be an Integer. And the second one is a String. Period." No
discussion. I explained our duck-typing paradigm. He's not
convinced. He thinks Java. So, he gets Java.

Lets check the types of the arguments of a method call!

(Well, this article is not about type checking at all. It's
about how to implement such a type checker. Or, more general,
it's about monitoring-functions.)

I wanted to do this with a nice and clean implementation, with
the real magic pushed down to a place I would never come again
(write once, read never). I wanted something like this (focus
on line 8):

1 class Foo
2 def bar(x, y, z)
3 # x should be Numeric
4 # y should be a String
5 # z should respond to :to_s
6 end
7
8 check_types :bar, Numeric, String, :to_s
9 end

(Focus on line 8, once again. Make it three times. It's all
about line 8.)

That was good enough for him. "But you can't do this. You
simply can't. That's magic." I laughed at him, turned around
and did it...

That's where this story is all about. To be more accurate: It's
about how I did it, about monitor-functions and
method-wrapping, not about type-checking.

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


21 Answers

Edgardo Hames

7/21/2006 8:58:00 PM

0

On 7/21/06, Erik Veenstra <erikveen@gmail.com> wrote:
> Do you remember the discussion about monitor-functions and
> metameta-programming?
>
> Well, I've completely rewritten this Module#wrap_method. It
> should be more robust now: thread-safety, better execution
> order of recursively wrapped methods, better execution order of
> method-is-defined-in-superclass, etc.

I just read your paper and it seems very interesting. Please, see my
comments below.

> I added some convenience methods as well: Module#pre_condition
> and Module#post_condition. These are really easy to use!

IMHO these functions need to be renamed. Those names remind me of DBC
and do not fully reflect what they do. Moreover, some kind of DBC
could be implemented using this module and hence you'd have a name
clash.

Good work!

Ed
--
Encontrá a "Tu psicópata favorito" http://tuxmaniac.bl...

Thou shalt study thy libraries and strive not to reinvent them without cause,
that thy code may be short and readable and thy days pleasant and productive.
-- Seventh commandment for C programmers

I have made this letter longer than usual because I lack the time to
make it shorter.
-- Blaise Pascal

Erik Veenstra

7/22/2006 11:06:00 AM

0

> > Do you remember the discussion about monitor-functions and
> > metameta-programming?
> >
> > Well, I've completely rewritten this Module#wrap_method. It
> > should be more robust now: thread-safety, better execution
> > order of recursively wrapped methods, better execution
> > order of method-is-defined-in-superclass, etc.

I'm still fighting the method-is-defined-in-module situation...

> > I added some convenience methods as well:
> > Module#pre_condition and Module#post_condition. These are
> > really easy to use!
>
> IMHO these functions need to be renamed. Those names remind
> me of DBC and do not fully reflect what they do. Moreover,
> some kind of DBC could be implemented using this module and
> hence you'd have a name clash.

I'll alias them... ;] (pre_action and post_action?)

> Good work!

Meanwhile, I use it all over: logging, counting, caching,
statistics. It's all done without touching the original code.
Feels good...

gegroet,
Erik V. - http://www.erikve...


Trans

7/22/2006 12:10:00 PM

0

Hi Erik,

Nice write-up. A couple of thoughts...

I think pre_ and post_condition is a bit, um... non-Rubyish, I guess is
the best way to put it. I don't think the reciever of the wrap should
be an argument. Instead just let it be ther reciever of the pre_ call.
For example Instead of:

def def_types(*types)
pre_condition(Module, :method_added) do |*args|
...

try

def def_types(*types)
Module.pre_condition(:method_added) do |*args|
...

Yes, that would mean the pre_ methods are public, but does it matter
since that's what you're doing anyway. Of course you could always use
#send too.

Also, #wrap_method seems like it could do with some simplification.
Taking that to the furthest case, is their a reason the the following
definition isn't enough?

def wrap_method( sym, &blk )
raise ArgumentError, "method does not exist" unless
method_defined?( sym )
old = instance_method(sym)
define_method(sym) { |*args| blk.call(old.bind(self), *args) }
end

Thanks,
T.


Erik Veenstra

7/22/2006 1:21:00 PM

0

> I don't think the reciever of the wrap should be an argument.
> Instead just let it be ther reciever of the pre_ call. For
> example Instead of:
>
> pre_condition(Module, :method_added) do |*args|
>
> Module.pre_condition(:method_added) do |*args| # NOT THE SAME

Module is not the receiver! The argument Module is just an
indicator (stupid abuse), so pre_condition knows that is has to
wrap a module method (with "class << self" in
wrap_module_method) instead of an instance method.

I should have called it pre_module_condition. (Like
wrap_module_method instead of wrap_method.) I was just sick of
creating more methods... ;]

> Also, #wrap_method seems like it could do with some
> simplification. Taking that to the furthest case, is their a
> reason the the following definition isn't enough?
>
> def wrap_method( sym, &blk )
> raise ArgumentError, "method does not exist" unless method_defined?( sym )
> old = instance_method(sym)
> define_method(sym) { |*args| blk.call(old.bind(self), *args) }
> end

Well, life isn't that easy... (Have a look at the code below.)
First thoughts:

* You definitely want to pass the block from the original
invocation to the original definition... Really... (In Ruby
1.9, this could be done your way, since blocks do get blocks.
But not in Ruby 1.8.)

* I wanted to be able to wrap non-existing methods.

* When wrapping :initialize in Bar, your wrap_method complains
with "method does not exist".

* When wrapping methods in both the class and the superclass,
going up and down, you can't (at wrap-time) determine the
order in which the blocks are to be executed (at run-time).
If I run the code below with your wrap_method (after removing
the "method does not exist" check), this :wrap_Foo is gone!
It's not what _I_ expected... ;] (See [1] for a more
complicated example. I'll try to make a diagram.)

It's complicated stuff... I tried to hide this complexity for
the user, by providing a clean interface (wrap_method) and an
even cleaner interface (pre_condition). I hope you find them
easy to use.

gegroet,
Erik V. - http://www.erikve...

[1] http://www.erikve...monitorfunctions/index.html#5.1.0

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

class Foo
def initialize(&block)
p [:initialize, block]
end
end

class Bar < Foo
wrap_method(:initialize) do |m, *a|
p [:wrap_Bar]

m.call(*a)
end
end

class Foo
wrap_method(:initialize) do |m, *a|
p [:wrap_Foo]

m.call(*a)
end
end

Bar.new{}

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


Trans

7/22/2006 1:59:00 PM

0


Erik Veenstra wrote:
> > I don't think the reciever of the wrap should be an argument.
> > Instead just let it be ther reciever of the pre_ call. For
> > example Instead of:
> >
> > pre_condition(Module, :method_added) do |*args|
> >
> > Module.pre_condition(:method_added) do |*args| # NOT THE SAME
>
> Module is not the receiver! The argument Module is just an
> indicator (stupid abuse), so pre_condition knows that is has to
> wrap a module method (with "class << self" in
> wrap_module_method) instead of an instance method.
>
> I should have called it pre_module_condition. (Like
> wrap_module_method instead of wrap_method.) I was just sick of
> creating more methods... ;]

Sorry, I misprepresnted what I meant. Use "aModule" instead of
"Module". -- You created a special method: #wrap_module_method for
doing #wrap_method external to the a module/class, but you could just
make it public (or use send):

aModule.wrap_method

And likewise

aModule.pre_condition
aModule.post_condition

> > Also, #wrap_method seems like it could do with some
> > simplification. Taking that to the furthest case, is their a
> > reason the the following definition isn't enough?
> >
> > def wrap_method( sym, &blk )
> > raise ArgumentError, "method does not exist" unless method_defined?( sym )
> > old = instance_method(sym)
> > define_method(sym) { |*args| blk.call(old.bind(self), *args) }
> > end
>
> Well, life isn't that easy... (Have a look at the code below.)
> First thoughts:
>
> * You definitely want to pass the block from the original
> invocation to the original definition... Really... (In Ruby
> 1.9, this could be done your way, since blocks do get blocks.
> But not in Ruby 1.8.)
>
> * I wanted to be able to wrap non-existing methods.
>
> * When wrapping :initialize in Bar, your wrap_method complains
> with "method does not exist".

I see. I'm recall considering this before. Obviously I had gone the
other direction, becuase then hwy use def at all, alwasy use
#wrap_method (closure issues not with standing). That reminds me of an
older notaiotn of mine where 'def' itself would actually wrap a
prexisting method automatically and you'd use #super to call it. But I
digress...

> * When wrapping methods in both the class and the superclass,
> going up and down, you can't (at wrap-time) determine the
> order in which the blocks are to be executed (at run-time).
> If I run the code below with your wrap_method (after removing
> the "method does not exist" check), this :wrap_Foo is gone!
> It's not what _I_ expected... ;] (See [1] for a more
> complicated example. I'll try to make a diagram.)

Owww! Nice catch. I hadn't though of that.

> It's complicated stuff... I tried to hide this complexity for
> the user, by providing a clean interface (wrap_method) and an
> even cleaner interface (pre_condition). I hope you find them
> easy to use.

Indeed. I'm going to work with this see what I can incoprate into
Facets', if that's cool with you.

Thanks,
T.


Erik Veenstra

7/22/2006 2:25:00 PM

0

> Sorry, I misprepresnted what I meant. Use "aModule" instead
> of "Module". -- You created a special method:
> #wrap_module_method for doing #wrap_method external to the a
> module/class, but you could just make it public (or use
> send):
>
> aModule.wrap_method

Wrong!... ;] The receiver of wrap_method is *always* a
module/class. When wrapping an instance method, with
wrap_method, the receiver is a module. When wrapping a module
method, with wrap_module_method, the receiver is the singleton
class of that module.

So, you should write aModule.meta_class.wrap_method (using
Why's little meta library...). Or, in default Ruby:

class << aModule
self
end.instance_eval
wrap_method(method_name){...}
end

I don't want to see this in regular, every day Ruby code.
That's why I came up with this wrap_module_method and this
stupid Module argument thing. ;] Kind of shortcuts...

> ...becuase then hwy use def at all, alwasy use
> #wrap_method...

;]

> > * When wrapping methods in both the class and the superclass,
> > going up and down, you can't (at wrap-time) determine the
> > order in which the blocks are to be executed (at run-time).
> > If I run the code below with your wrap_method (after removing
> > the "method does not exist" check), this :wrap_Foo is gone!
> > It's not what _I_ expected... ;] (See [1] for a more
> > complicated example. I'll try to make a diagram.)
>
> Owww! Nice catch. I hadn't though of that.

;]

> > It's complicated stuff... I tried to hide this complexity
> > for the user, by providing a clean interface (wrap_method)
> > and an even cleaner interface (pre_condition). I hope you
> > find them easy to use.
>
> Indeed. I'm going to work with this see what I can incoprate
> into Facets', if that's cool with you.

Sure. Everybody is free to copy the code and use it for
whatever reason. If you make money with it, please call me...

gegroet,
Erik V. - http://www.erikve...


Trans

7/22/2006 2:40:00 PM

0


Erik Veenstra wrote:
> > Sorry, I misprepresnted what I meant. Use "aModule" instead
> > of "Module". -- You created a special method:
> > #wrap_module_method for doing #wrap_method external to the a
> > module/class, but you could just make it public (or use
> > send):
> >
> > aModule.wrap_method
>
> Wrong!... ;] The receiver of wrap_method is *always* a
> module/class. When wrapping an instance method, with
> wrap_method, the receiver is a module. When wrapping a module
> method, with wrap_module_method, the receiver is the singleton
> class of that module.
>
> So, you should write aModule.meta_class.wrap_method (using
> Why's little meta library...). Or, in default Ruby:
>
> class << aModule
> self
> end.instance_eval
> wrap_method(method_name){...}
> end
>
> I don't want to see this in regular, every day Ruby code.
> That's why I came up with this wrap_module_method and this
> stupid Module argument thing. ;] Kind of shortcuts...

Well, that's the thing. I'd rather do

aModule.meta.wrap_method
aModule.meta.pre_condition

Then have all these different useage forms:

wrap_method
wrap_module_method
pre_condition( aModule,

T.

> > > * When wrapping methods in both the class and the superclass,
> > > going up and down, you can't (at wrap-time) determine the
> > > order in which the blocks are to be executed (at run-time).
> > > If I run the code below with your wrap_method (after removing
> > > the "method does not exist" check), this :wrap_Foo is gone!
> > > It's not what _I_ expected... ;] (See [1] for a more
> > > complicated example. I'll try to make a diagram.)
> >
> > Owww! Nice catch. I hadn't though of that.
>
> ;]

On looking at it again, I think it goes back to you wnating to wrap
methods that aren;t there. When subclass, there's not much use in
wrapping when one can just define the method and call super.

> Sure. Everybody is free to copy the code and use it for
> whatever reason. If you make money with it, please call me...

Money? Don't make it.

T.


Erik Veenstra

7/22/2006 4:18:00 PM

0

> > So, you should write aModule.meta_class.wrap_method (using
> > Why's little meta library...).
>
> Well, that's the thing. I'd rather do
>
> aModule.meta.wrap_method
> aModule.meta.pre_condition

I agree. I added it to my (not-yet-published) version.

What about the abuse of Array and Object, as arguments?

gegroet,
Erik V. - http://www.erikve...


Trans

7/22/2006 9:00:00 PM

0


Erik Veenstra wrote:
> > > So, you should write aModule.meta_class.wrap_method (using
> > > Why's little meta library...).
> >
> > Well, that's the thing. I'd rather do
> >
> > aModule.meta.wrap_method
> > aModule.meta.pre_condition
>
> I agree. I added it to my (not-yet-published) version.
>
> What about the abuse of Array and Object, as arguments?

I'm not sure the Array abuse is needed if you limit the arguement to
the array itself, eg. don;t splat it whencalling the block. The reason
is that block unlik lambdas are more flexiable with array argument and
cna automatically splt them. Then you can just use #replace tpo change
the args if you want:

$a = ["a", "b", "c"]

def c(&block)
block.call( $a )
end

c { |a| p a.replace([2,3]) }

$a #=> [2, 3]

But also,

c { |a,b| p a+b } #=> 5

I'll have to look at the Object abuse again and get back to you.

T.


Erik Veenstra

7/23/2006 12:24:00 PM

0

> Do you remember the discussion about monitor-functions and
> metameta-programming?
>
> The article and the code are here:
>
> http://www.erikve...monitorfunctions/...

I've updated the code. Better readable. Added metaclass.
Removed wrap_module_method. Added true and false to once.

Please, keep shooting...

Thanks.

gegroet,
Erik V. - http://www.erikve...