Meta-Meta-Programming, revisited

E

Erik Veenstra

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

----------------------------------------------------------------
 
E

Edgardo Hames

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
 
E

Erik Veenstra

Do you remember the discussion about monitor-functions and
I'm still fighting the method-is-defined-in-module situation...
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/
 
T

transfire

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

Erik Veenstra

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/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{}

----------------------------------------------------------------
 
T

transfire

Erik said:
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
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.
 
E

Erik Veenstra

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

transfire

Erik said:
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.
 
E

Erik Veenstra

So, you should write aModule.meta_class.wrap_method (using
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/
 
T

transfire

Erik said:
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.
 
E

Erik Veenstra

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

True. I did that before. But receiving |*args| in blocks is so
common (for me), that I was typing it again and again,
introducing bug after bug. That's why I introduced this
Array-parameter-thing.
I'll have to look at the Object abuse again and get back to
you.

Please.

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
T

Trans

Erik said:
True. I did that before. But receiving |*args| in blocks is so
common (for me), that I was typing it again and again,
introducing bug after bug. That's why I introduced this
Array-parameter-thing.

I see. But |*args| does work just so long as you don't want to _change
the args_, doesn't it?

Okay. Looking at your "Object" example:

require "ostruct"
class Foo < Struct.new:)aa, :bbb)
post_condition:)initialize, Object) do
line = caller.select{|s|
s.include?(__FILE__)}.shift.scan(/\d+/)[-1]

puts "An object of class #{self.class} has been created."
puts "The arguments are: @aa=#{aa} and @bbb=#{bbb}."
puts "The object has id: #{__id__}."
puts "It's done on line: #{line}."
puts
end
end

So you're confining the context of excution, shutting out the defining
closure. Correct? It seems reasonable, though couldn't one do that on
their own if they wanted? Eg.

class Foo < Struct.new:)aa, :bbb)
post_condition:)initialize) do
instance_eval do
line = caller.select{|s|
s.include?(__FILE__)}.shift.scan(/\d+/)[-1]

puts "An object of class #{self.class} has been created."
puts "The arguments are: @aa=#{aa} and @bbb=#{bbb}."
puts "The object has id: #{__id__}."
puts "It's done on line: #{line}."
puts
end
end
end

These parameter "abuses" certainly make particular uses more succinct,
but on the downside, they are more syntax to learn, as opposed to being
able to draw upon what one already knows about Ruby.

T.
 
E

Erik Veenstra

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

I've added the diagram [1]... ;]

gegroet,
Erik V. - http://www.erikveen.dds.nl/

[1] file://localhost/home/erik/web/monitorfunctions/index.html#5.1.0
 
E

Erik Veenstra

I see. But |*args| does work just so long as you don't want
to _change the args_, doesn't it?

It's possible (not tested) to change the members of *args, but
not *args itself.
So you're confining the context of excution, shutting out the
defining closure. Correct? It seems reasonable, though
couldn't one do that on their own if they wanted? Eg.

class Foo < Struct.new:)aa, :bbb)
post_condition:)initialize) do
instance_eval do

Doesn't work. You should have passed the object (the receiver
of instance_eval) to the block. Which doesn't work nicely in
combination with *args...
These parameter "abuses" certainly make particular uses more
succinct, but on the downside, they are more syntax to learn,
as opposed to being able to draw upon what one already knows
about Ruby.

True. There's a lot of context switching below the surface.
wrap_method and *_condition should take care of that. By
default, the given block should behave as expected: It has its
own context, as in plain Ruby. But you _could_ execute it in
the context of the object (by passing Object).

We can get rid of the abuse of Array, but it's just more common
to see |*args| then |args|.

What about this?: We could remove both abuses by passing both
the arguments and the object: |args, obj|. Or even this: |args,
block, obj|. (Which drags block into the game...)

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
7

7rans

Erik said:
What about this?: We could remove both abuses by passing both
the arguments and the object: |args, obj|. Or even this: |args,
block, obj|. (Which drags block into the game...)

More flexible and less "special". Seems like a good idea. Like the
diagram btw.

T.
 
E

Erik Veenstra

More flexible and less "special". Seems like a good idea.

What about |obj, method_name, args, block|? That one must be
easy to remember, since it resembles the order of the call
(object is the receiver).
Like the diagram btw.

;]

Last night, I glanced over the front page of the programming
section of Reddit, like I do every night. There it was: "Ruby
Monitor-Functions, Or Meta-Meta-Programming in Ruby"! Very
funny... ;]

gegroet,
Erik V. - http://www.erikveen.dds.nl/

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

post_condition:)description) do |obj, method_name, args, block|
obj.instance_eval do
puts @text
end
end

----------------------------------------------------------------
 
7

7rans

Erik said:
More flexible and less "special". Seems like a good idea.

What about |obj, method_name, args, block|? That one must be
easy to remember, since it resembles the order of the call
(object is the receiver).
Like the diagram btw.

;]

Last night, I glanced over the front page of the programming
section of Reddit, like I do every night. There it was: "Ruby
Monitor-Functions, Or Meta-Meta-Programming in Ruby"! Very
funny... ;]

gegroet,
Erik V. - http://www.erikveen.dds.nl/

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

post_condition:)description) do |obj, method_name, args, block|
obj.instance_eval do
puts @text
end
end

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

Very nice.

Not sure how the method_name parameter is useful, isn't it just
:description? In any case, the paraemters are nicely intuitive!

T.
 

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

Similar Threads

Meta-Meta-Programming 29
Beginning meta-programming question 1
#meta 15
Java programming 1
Automatic setup of meta path hooks? 0
Help with simple meta programming 1
meta reflection in c++ 0
meta pre-processor 23

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top