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!

(This post 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 7):

     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     typed :bar, Numeric, String, :to_s   # !!!!!
     8   end

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

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

First, I'll give you a piece of code which doesn't do anything,
except that it seems to wrap the original method in another
method (focus on line 12):

     1   class Module
     2     def just_wrap(method_name)
     3       wrap_method(method_name) do |org_method, args, block|
     4         org_method.call(*args, &block)
     5       end
     6     end
     7   end
     8   class Foo
     9     def bar(x, y, z)
    10       p [x, y, z]
    11     end
    12     just_wrap :bar   # !!!!!
    13   end
    14   Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"]

You can find the implementation of wrap_method below. This
thread is all about that very one method. It's the big trick.
You don't need to understand its implementation. Knowing how to
use it is good enough.

Line 3 retrieves the original method and yields the given block
with this method, as well as with its arguments and block. Not
*args, not &block. Just args and block. Blocks don't get
blocks, you know. (Although it's introduced in Ruby 1.9.)

Within the given block, we can do whatever we want to. That's
where the real stuff goes.

But, someday, we have to call the original method with the
original parameters and the original block. That's what we do
on line 4.

That's about it. That's the whole story. There's nothing more
to say.

Except for an example or two...

Here's a simple example. It "upcases" every argument. It must
be silly to "upcase" every argument like this, but we'll do it
anyway. Introducing line 4:

     1   class Module
     2     def big_arguments(method_name)
     3       wrap_method(method_name) do |org_method, args, block|
     4         args = args.collect{|x| x.to_s.upcase}
     5         org_method.call(*args, &block)
     6       end
     7     end
     8   end
     9   class Foo
    10     def bar(x, y, z)
    11       [x, y, z]
    12     end
    13     big_arguments :bar
    14   end
    15   Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"]

Here's another example. Lines 4, 5 and 6. They inform you about
nil things.

     1  class Module
     2    def find_nil(method_name)
     3      wrap_method(method_name) do |org_method, args, block|
     4        if args.include?(nil)
     5          $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
     6        end
     7        org_method.call(*args, &block)
     8      end
     9    end
    10  end
    11  class Foo
    12    def bar(x, y, z)
    13    end
    14    find_nil :bar
    15  end
    16  Foo.new.bar("a", "b", "c") # ===>
    17  Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"].
    18  Foo.new.bar("a", "b", "c") # ===>

I call "typed", "just_wrap", "big_arguments" and "find_nil":
monitor-functions. I don't know exactly how this term got into
my head, but it does sound good: monitor-functions. It's
definitely better than wrap-method-functions. (You can build
non-monitor-functions as well. But that's really stupid:
monitor-and-non-monitor-functions.)

Meanwhile, I played with a couple of monitor-functions:
debugging, logging, synchronization, statistics, benchmarking,
roles (like on WebSphere). Ideas? It's easy to create them. Try
it. Let me know.

Forget about the implementation of "wrap_method". It's just
sitting there, waiting to be used to implement a
monitor-function. It's easy to implement a monitor-function.
And it's very, very easy to use it. Those where my goals.

Oh, by the way, if such a monitor-function is kind of
meta-programming (it's a buzz-word, I know, but it is, isn't
it?), how would you call "wrap_method"? Meta-meta-programming?

It was just an idea. Just wanted to tell you. Couldn't sleep.
Feel much better now. Maybe I can sleep...

Thanks for listening.

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

PS: Sorry for this rather lengthy post. It just got a bit
    lengthier than I planned. It just happened. No control.

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

 class Module

         # With this, we can create monitoring functions.
         # It might not be clearly readable,
         # but it's written only once.
         # Write once, read never.
         # Forget about the internals.
         # Just use it.
         # It should be part of Ruby itself, anyway... :)

   def wrap_method(method_name, *args1, &block1)
     @_wrap_method_count_ ||= 0
     @_wrap_method_count_ += 1

     prefix = "_wrap_method_#{@_wrap_method_count_}"

     module_eval <<-EOF
       alias :#{prefix}_org :#{method_name}                              # Store the original method for later use.

       define_method(:#{prefix}_args)    {args1}                         # Store the arguments of the call to Module#wrap_method. (Not used.)
       define_method(:#{prefix}_block)   {block1}                        # Store the block of the call to Module#wrap_method.

       def #{method_name}(*args2, &block2)
         #{prefix}_block.call(method(:#{prefix}_org), args2, block2)     # Note that this is not *args2 and not &block2!
       end
     EOF
   end

 end

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