Meta-Meta-Programming

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

   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

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

I forgot to show you the implementation of this "typed".

Well, here it is...

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

···

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

         # IMPLEMENTATION

class Module
   def typed(method_name, *types)
     wrap_method(method_name) do |org_method, args, block|
       args.each_with_index do |args, n|
         [types[n]].flatten.each do |typ|
           if typ.kind_of?(Module)
             unless arg.kind_of?(typ)
               raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
             end
           elsif typ.kind_of?(Symbol)
             unless arg.respond_to?(typ)
               raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
             end
           else
             raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
           end
         end
       end

       org_method.call(*args, &block)
     end
   end
end

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

         # TEST SCRIPT

class Foo
   def bar(x, y, z)
     # x should be Numeric
     # y should be a String
     # z should respond to :gsub and :to_s
     :good
   end

   typed :bar, Numeric, String, [:gsub, :to_s]
end

def test(*args)
   begin
     puts "#{args.inspect} : OK : #{Foo.new.bar(*args).inspect}"
   rescue Exception => e
     puts "#{args.inspect} : NOK : #{e.message}"
   end
end

puts
puts File.open(__FILE__){|f| f.readlines}.select{|x| x =~ /^\s*typed\b/}.join("\n")
puts

test(7)
test(7, 8, 9)
test(7, 8, "9")
test(7, "8", 9)
test(7, "8", "9")

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

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

   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

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

For bonus points, record stats for every time your assertion fails and you generate a "type error" compared with every time it does nothing. Hopefully you can show your coworker how useless the code really is.

···

On Feb 7, 2006, at 2:18 PM, 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...

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

http://www.rcrchive.net/rcr/show/321

Erik Veenstra a écrit :

     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"]

class Foo
  def bar(x, y, z)
    p [x, y, z]
  end
end

cut JustWrap < Foo
  def bar
    super
  end
end

     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"]

class Foo
  def bar(x, y, z)
    p [x, y, z]
  end
end

cut BigArguments < Foo
  def bar(*args)
    super(*args.collect{|x| x.to_s.upcase})
  end
end

     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") # ===>

class Foo
  def bar(x, y, z)
  end
end

cut FindNil < Foo
def bar(*args)
  if args.include?(nil)
   $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
  end
  super(*args)
end
end

···

--
Lionel Thiry

Personal web site: http://users.skynet.be/lthiry/

you should consider ara's 'traits' library, too, for Java Joe.
http://codeforpeople.com/lib/ruby/traits/

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

   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

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

Did I mention that it is possible to double-wrap a method with
two or more monitor-functions? The order in which they are
executed is down-up:

class Foo
   def bar(x, y, z)
     # x should be Numeric
     # y should be a String
     # z should respond to :to_s and :gsub
   end
   typed :bar, Numeric, String, [:to_s, :gsub]
   log_args :bar
end

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

The story continues...

Now, we have this Module#wrap_method for wrapping instance
methods. But what about wrapping module methods, like
Module#wrap_module_method?

We are going to take this dangerous type checking thing to the
next level. Just as an example. JUST AS AN EXAMPLE!!!

Imagine, we want to move the types to be positioned before the
method instead of after the method. Just for better
readability:

class Foo
   def_types Numeric, String, [:to_s, :gsub]
   def bar(x, y, z)
     # x should be Numeric
     # y should be a String
     # z should respond to :to_s and :gsub
     # Very long method...
   end
end

.... instead of:

class Foo
   def bar(x, y, z)
     # x should be Numeric
     # y should be a String
     # z should respond to :to_s and :gsub
     # Very long method...
   end
   typed :bar, Numeric, String, [:to_s, :gsub]
end

We can do this by storing the types in def_types and
overwriting Foo::method_added. But what about the old
functionality in Foo::method_added? Sure, alias to another
method and than use this alias. That's the common way to work
around this problem. (I've never liked it...) But, again, we
can use "wrap_method" to add the new functionality to the
original method. Introducing "wrap_module_method":

def def_types(*types)
   wrap_module_method(:method_added) do |org_method, args, block|
     if types
       method_name = args[0]
       t = types
       types = nil # Avoid looping
       typed(method_name, *t)
     end

     org_method.call(*args, &block) if org_method
   end
end

Do you see that this "wrap_module_method" looks like
"wrap_method"? They should look the same. They are brother and
sister.

Since we do a "wrap_module_method" in "method_added" and
"wrap_module_method" does a "wrap_method" and "wrap_method"
adds a method and thus does a "method_added", the wrapped
"method_added" gets called over and over again. That's why I
introduced this loop-avoiding-system. You only have to add this
mechanism when wrapping Module#method_added, not for other
module methods.

Once again, it should be possible to wrap the wrapper:

class Foo
   def_types Numeric, String, [:to_s, :gsub]
   def_stat "/tmp/stats.log"

   def bar1(x, y, z)
   end

   def bar2(x, y, z) # bar2 is neither logged, nor checked.
   end
end

See below for a full implementation of both wrapping methods
and of both type checking methods.

(Once again, it's not about this type checking, or duck-type
checking: it's all about "wrap_method" and
"wrap_module_method".)

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

···

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

class Module

         # Meta-Meta-Programming

         # 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... :slight_smile:

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

     prefix = "_wrap_method_#{@_wrap_method_count_}"

     module_eval <<-EOF
       if instance_methods.include?(:#{method_name}.to_s)
         alias :#{prefix}_org :#{method_name} # Store the original method for later use.
       end

       define_method(:#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.

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

   def wrap_module_method(method_name, &block1)
     class << self
       self
     end.module_eval do
       wrap_method(method_name) do |org_method, args2, block2|
         block1.call(org_method, args2, block2)
       end
     end
   end

end

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

class Module

         # Type checking.
         # Or duck-type checking.

         # Example:
         # class Foo
         # def_types String, Numeric, [:to_s, :gsub]
         # def :bar(x, y, x)
         # # x should be Numeric
         # # y should be a String
         # # z should respond to :to_s and :gsub
         # end
         # end

   def def_types(*types)
     wrap_module_method(:method_added) do |org_method, args, block|
       if types
         method_name = args[0]
         t = types
         types = nil # Avoid looping
         typed(method_name, *t)
       end

       org_method.call(*args, &block) if org_method
     end
   end

         # Example:
         # class Foo
         # def :bar(x, y, x)
         # # x should be Numeric
         # # y should be a String
         # # z should respond to :to_s and :gsub
         # end
         # typed :bar, String, Numeric, [:to_s, :gsub]
         # end

   def typed(method_name, *types)
     wrap_method(method_name) do |org_method, args, block|
       args.each_with_index do |arg, n|
         [types[n]].flatten.each do |typ|
           if typ.kind_of?(Module)
             unless arg.kind_of?(typ)
               raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
             end
           elsif typ.kind_of?(Symbol)
             unless arg.respond_to?(typ)
               raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
             end
           else
             raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
           end
         end
       end

       org_method.call(*args, &block)
     end
   end

end

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

class Foo
   def bar(x, y, z)
     p [x, y, z]
   end
end

cut JustWrap < Foo
   def bar
     super
   end
end

So I have to replace every Foo.new to JustWrap.new, just to
activate the debugging? Kidding?

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

But this is an example of why you wouldn't really want this
functionality in Ruby right? We all know there are times we need to
contrain arguments, but that should be exception, not the norm. Hence
the beauty of ducktyping.

T.

So, just for clarifications sake: Both the presented wrapping method
and the cut library used earlier are implementations (generic uses?)
of Aspect Oriented Programming, right?

Nothing *more* than that going on, correct?

Vidar, I think the question there is: Should I rely on a type/method
check to ensure that I don't get bad parameters or should I just write
some tests to make sure that the code in question fails in a sensible
way when those expectations aren't met. Those would be edge cases
after all, and you'd have to write the tests for them anyway.

To me, it seems to be unDRY...

Except if you were using it to enhance performance through typing
related optimizations.

···

On 2/8/06, Erik Veenstra <google@erikveen.dds.nl> wrote:

Did I mention that it is possible to double-wrap a method with
two or more monitor-functions? The order in which they are
executed is down-up:

class Foo
   def bar(x, y, z)
     # x should be Numeric
     # y should be a String
     # z should respond to :to_s and :gsub
   end
   typed :bar, Numeric, String, [:to_s, :gsub]
   log_args :bar
end

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

--
-Dan Nugent

Oops! A little typo...

args.each_with_index do |args, n|

....should be:

args.each_with_index do |arg, n|

def def_types(*types)
   wrap_module_method(:method_added) do |org_method, args, block|
     if types
       method_name = args[0]
       t = types
       types = nil # Avoid looping
       typed(method_name, *t)
     end

     org_method.call(*args, &block) if org_method
   end
end

Since we do a "wrap_module_method" in "method_added" and
"wrap_module_method" does a "wrap_method" and "wrap_method"
adds a method and thus does a "method_added", the wrapped
"method_added" gets called over and over again. That's why I
introduced this loop-avoiding-system. You only have to add
this mechanism when wrapping Module#method_added, not for
other module methods.

A bit more readable:

"def_types" wraps "method_added."

From now on, every time "method_added" is called, it calls
"typed" (if we remove "if types"), which calls "wrap_method,"
which adds methods and thus triggers "method_added." And so on.

This "types = nil", also applies the wrapping to *only* the
next method (only to Foo#bar1 and not to Foo#bar2).

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

How is the term "monitor-functions" you have used in this thread different from the standard "higher-order functions," if you don't mind my asking?

James Edward Gray II

···

On Feb 8, 2006, at 7:48 AM, Erik Veenstra wrote:

Did I mention that it is possible to double-wrap a method with
two or more monitor-functions?

Very nice.

I once used a similar technique to wrap all calls to the logger. Then,
if the application threw an exception, the crash report would include
all of the recent log entries (even ones that were marked DEBUG and
hence not saved to disk).

As an aside, this really highlights some of the cultural differences
between Ruby and other developers regarding what defines "simplicity".
You only need to write one line to wrap. But, if you want to know
what's going on, you get:

  # but it's written only once.
  # Write once, read never.
  # Forget about the internals.
  # Just use it.

Rubyists usually define this as simple.

Other developers don't:
http://www.artima.com/intv/simplexity.html
Anders Hejlsberg: Let me first talk a little bit about how I view
simplicity in general. No one ever argues that simplicity isn't good,
but people define simplicity in a variety of ways. There's one kind of
simplicity that I like to call simplexity. When you take something
incredibly complex and try to wrap it in something simpler, you often
just shroud the complexity. You don't actually design a truly simple
system. And in some ways you make it even more complex, because now the
user has to understand what was omitted that they might sometimes need.
That's simplexity

I think this highlights the key cultural difference between Ruby &
Python, and shows why Rubyists love all the meta programming, class
methods, DSL (like Rails' has_one etc.), and the like, while
Pythonistas avoid them.

So I have to replace every Foo.new to JustWrap.new, just to
activate the debugging? Kidding?

No you do not. A cut is a _transparent_ class. You would still use
Foo.new.

T.

Trans wrote:

But this is an example of why you wouldn't really want this
functionality in Ruby right? We all know there are times we need to
contrain arguments, but that should be exception, not the norm. Hence
the beauty of ducktyping.

That's somewhat short-sighted. Any method in a language like Ruby will
either not be fully defined (that is, it will fail for a subset of
possible inputs), or will be full of explicit type checking (kind_of?
etc). For the most part, the former is chosen. For the most part it
"works", sort of.

But it works because most of the time people have reasonable
expectations of what a method will expect, or you read the
documentation (and it is up to date enough) and you test.

However this is nothing more than making Ruby enforce a contract: If
your method DOES need #to_s to be present for one of the arguments for
the method to be well defined, I for one would prefer to find out as
early as possible rather than have it suddenly break on me because
things just happens to work without it "most of the time".

Properly written preconditions both reduces the test cases - the set of
different classes of input can be constrained significantly - and helps
document the code _and_ ensure that this documentation is likely to
stay in sync with the code, unlike documentation that has no effect on
your tests.

There's nothing contradictory between this method and ducktyping -
ducktyping is about not relying on name tags but about actual features
(that is, it's having the #to_s method that is important, not having
been labeled as implementing a hypotetical "CanConvertToString"
interface), and this method can be used to check for that.

It can of course also be abused to make type checks that are far too
generic, and I can to a certain extent agree with you that using it to
constrain arguments to a specific class may be undesirable most of the
time (... after having seen full well how annoying badly done type
checking is from dealing with REXML...)

Consider it inline documentation and an additional testing and
debugging tool - if runtime performance is affected too much you could
always add a switch to make it only actually wrap the methods if $DEBUG
is set and otherwise leave them alone.

Vidar

Daniel Nugent wrote:

Vidar, I think the question there is: Should I rely on a type/method
check to ensure that I don't get bad parameters or should I just write
some tests to make sure that the code in question fails in a sensible
way when those expectations aren't met. Those would be edge cases
after all, and you'd have to write the tests for them anyway.

To me, it seems to be unDRY...

To me the issue is to avoid surprises. If your function will need a
specific method every few million times it is executed, or on specific
dates, or when a specific race condition occurs, or when processing
specific user input, it might require a lot of work for a user of your
code to verify that their application works as expected through testing
unless they know exactly what they need to test for.

More importantly: Unless _they_ verify these preconditions in their
test cases they will have to handle whatever you consider a "sensible
way of failing". If your failure mode doesn't match their expectations,
it might take a lot of work to set verify that there is actually a
problem, and it can easily slip through.

This is a pragmatic way of ensuring the least possibility of surprise,
by forcing a failure as early as possible. The other alternative is to
document these cases painstakingly and depend on the users of your code
to test for them. But why put your users through that pain if you have
an easy way of trapping the error early on that at the same time serves
as explicit documentation of what your code expects?

I am not saying it's always what you want, or that you'll always see
benefits from it. But there are certainly cases where the potential
problems caused by a failure are severe enough that it is better to
cause a failure early on. If I am going to do batch database updates on
a millions of rows for instance, I'd much prefer to find corner cases
right away during testing, than risk having the code fail with a
NoMethodError two days into a production run because I hit a bizarre
corner case.

It's not always a case of "just writing some unit tests" unless you
first spend ages analysing the code you are calling to verify exactly
how to trigger all corner cases.

Simplifying unit tests is exactly one of the compelling uses for this -
the earlier your methods explicitly check for and fail if preconditions
are not met, the smaller the input set you need to test is likely to
be.

It is even more compelling because it can be easily adapted so that it
is easy to turn off for production code if performance becomes an
issue: As I suggested, you could easily make the wrapper do nothing
unless $DEBUG is defined, for instance.

Vidar

Erik's "monitor-functions" would be a subset of (the more general)
higher-order functions (for a relaxed definition, accounting for the
fact that neither the input nor the output are actually functions, but
the symbols given as input map to methods, and the "output" is the
side-effect of changing a method definition).

The "type-checking monitors" would be noted as

env(#f=f1) x meth-name x arg-type-list -> env(#f=f2)
========= ============= =========
'world state' type-checking info new environment where
where #f is #f checks the args before
defined as f1 doing f1
(f1 would be the AST)

···

On Thu, Feb 09, 2006 at 01:44:46AM +0900, James Edward Gray II wrote:

On Feb 8, 2006, at 7:48 AM, Erik Veenstra wrote:

>Did I mention that it is possible to double-wrap a method with
>two or more monitor-functions?

How is the term "monitor-functions" you have used in this thread
different from the standard "higher-order functions," if you don't
mind my asking?

Higher-order function - Wikipedia

--
Mauricio Fernandez

How is the term "monitor-functions" you have used in this
thread different from the standard "higher-order functions,"
if you don't mind my asking?

A monitor-function isn't a higher-order function, since it
takes the name of a method, not the method itself. Nor does it
_return_ a function. Well, it does _create_ a new function as a
side effect, but that's not part of the definition of
higher-order functions. Higher-order functions are used in
Lisp. In functional programming (that's what you try to do in
Lisp) side-effects are considered bad. In OO (Ruby) it's
considered common. (Although monitor-functions are not
OO'ish...)

Module#wrap_method, used in the implementation of a
monitor-function, is a higher-order function: It takes a block.

So, the relation between monitor-functions and higher-order
functions that does exist, is invisible to the user of a
monitor-function.

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