Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Meta-Meta-Programming, revisited

Reply
Thread Tools

Meta-Meta-Programming, revisited

 
 
Erik Veenstra
Guest
Posts: n/a
 
      07-21-2006
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.erikveen.dds.nl/monitorfunctions/index.html

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.erikveen.dds.nl/

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

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 :

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.

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


 
Reply With Quote
 
 
 
 
Edgardo Hames
Guest
Posts: n/a
 
      07-21-2006
On 7/21/06, Erik Veenstra <(E-Mail Removed)> 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
--=20
Encontr=E1 a "Tu psic=F3pata favorito" http://tuxmaniac.blogspot.com

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

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

 
Reply With Quote
 
 
 
 
Erik Veenstra
Guest
Posts: n/a
 
      07-22-2006
> > 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.erikveen.dds.nl/


 
Reply With Quote
 
transfire@gmail.com
Guest
Posts: n/a
 
      07-22-2006
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.


 
Reply With Quote
 
Erik Veenstra
Guest
Posts: n/a
 
      07-22-2006
> 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.erikveen.dds.nl/

[1] http://www.erikveen.dds.nl/monitorfu...dex.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{}

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


 
Reply With Quote
 
transfire@gmail.com
Guest
Posts: n/a
 
      07-22-2006

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.


 
Reply With Quote
 
Erik Veenstra
Guest
Posts: n/a
 
      07-22-2006
> 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.erikveen.dds.nl/


 
Reply With Quote
 
transfire@gmail.com
Guest
Posts: n/a
 
      07-22-2006

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.


 
Reply With Quote
 
Erik Veenstra
Guest
Posts: n/a
 
      07-22-2006
> > 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.erikveen.dds.nl/


 
Reply With Quote
 
transfire@gmail.com
Guest
Posts: n/a
 
      07-22-2006

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.


 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
NVIDIA Forceware Revisited article Silverstrand Front Page News 0 09-22-2005 01:37 PM
Mnenhy revisited Caploc Firefox 3 12-16-2004 12:37 AM
ESMTP Problems Revisited Brian Cisco 9 01-06-2004 06:02 PM
REVISITED: Cisco to Dell PowerConnect via Fiber Brad Tarver Cisco 0 07-09-2003 12:01 AM
REVISITED: connecting a dell powerconnect 3024 to a cisco 2924M over fiber Brad Tarver Cisco 0 07-09-2003 12:00 AM



Advertisments