Meta-Meta-Programming, revisited

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.

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

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

···

On 7/21/06, Erik Veenstra <erikveen@gmail.com> wrote:
--
Encontrá a "Tu psicópata favorito" http://tuxmaniac.blogspot.com

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

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.

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

The article and the code are here:

Ruby Monitor-Functions - Or Meta-Meta-Programming in Ruby

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

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

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] Ruby Monitor-Functions - Or Meta-Meta-Programming in Ruby

···

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

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

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

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.

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

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/

<...>

I've added the diagram [1]... ;]
[1] file://localhost/home/erik/web/monitorfunctions/index.html#5.1.0

:slight_smile:
let it be this way: http://www.erikveen.dds.nl/monitorfunctions/index.html#5.1.0

···

2006/7/23, Erik Veenstra <erikveen@gmail.com>:

--
Regards,
Rimantas
--
http://rimantas.com/

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.

;]

Thanks.

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

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

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.

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/

Erik Veenstra wrote:

> 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 see. But |*args| does work just so long as you don't want to _change
the args_, doesn't it?

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

Please.

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.

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.

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

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/

Erik Veenstra wrote:

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.

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

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

Erik Veenstra wrote:

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