From: m4dc4p Date: 2006-02-08T07:53:23+09:00 Subject: Re: Meta-Meta-Programming Slick! Erik Veenstra wrote: > 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 > > ----------------------------------------------------------------