Forum: Ruby Meta-Meta-Programming

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-07 23:19
(Received via mailing list)
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
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-07 23:35
(Received via mailing list)
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")
573b9499030e1ccb867ef80f0ff1ac49?d=identicon&s=25 m4dc4p (Guest)
on 2006-02-07 23:56
(Received via mailing list)
Slick!
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-02-08 00:43
(Received via mailing list)
On Feb 7, 2006, at 2:18 PM, Erik Veenstra wrote:

> implement such a type checker. Or, more general, it's about
>      4       # y should be a String
> and did it...
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.

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

http://trackmap.robotcoop.com
Ede7f0028025fb88b47dda1a8c38be42?d=identicon&s=25 Lionel Thiry (Guest)
on 2006-02-08 00:49
(Received via mailing list)
http://www.rcrchive.net/rcr/show/321

Erik Veenstra a écrit :
>     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

>     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

>     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/
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 01:04
(Received via mailing list)
>  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/
Ce60c4f78a63b0695e4dafc4bd7964f7?d=identicon&s=25 Lou Vanek (Guest)
on 2006-02-08 01:22
(Received via mailing list)
you should consider ara's 'traits' library, too, for Java Joe.
http://codeforpeople.com/lib/ruby/traits/
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2006-02-08 01:35
(Received via mailing list)
> 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.
Db59927495b7db991b543d113e1835ff?d=identicon&s=25 Maurice Codik (Guest)
on 2006-02-08 01:56
(Received via mailing list)
Yet another suggestion-- I wrote a little library that lets you define
simple contracts for function inputs... useful if you dont want to
repeat
the same duck-type assertions over and over again.

here's an example of how it works:

require "contracts"
class TestContracts
  extend Contracts

  define_data :writable => lambda {|x| x.respond_to?("write") and
x.respond_to?("closed?") and not x.closed? },
              :positive => lambda {|x| x >= 0 }

  contract :hello, [:positive, :string, :writable]

  def hello(n, s, f)
    n.times { f.write "hello #{s}!\n" }
  end
end

tc = TestContracts.new
tc.hello(2, "world", $stdout)
# -> hello world!
# -> hello world!

# tc.hello(2, 3, $stdout)
#  -> test-contracts.rb:22: argument 2 of method 'hello' must satisfy
the 'string' contract (Contracts::ContractViolation)

You can download it at: :
http://mauricecodik.com/projects/ruby/contracts.rb

Maurice
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2006-02-08 03:06
(Received via mailing list)
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.
Caf3d97ceff60d6d105c45305d34658c?d=identicon&s=25 vidar.hokstad@gmail.com (Guest)
on 2006-02-08 12:40
(Received via mailing list)
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
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 14:35
(Received via mailing list)
Well written. Thanks. I'll forward it to Java Joe. :)

(Although my original post wasn't about type checking, or about
duck-type checking. It was about how to implement such a
wrapper...)

Tanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 14:50
(Received via mailing list)
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/
70c8da82d09d3866222976ab8978133c?d=identicon&s=25 Daniel Nugent (Guest)
on 2006-02-08 15:21
(Received via mailing list)
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.
Caf3d97ceff60d6d105c45305d34658c?d=identicon&s=25 vidar.hokstad@gmail.com (Guest)
on 2006-02-08 15:45
(Received via mailing list)
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
31ab75f7ddda241830659630746cdd3a?d=identicon&s=25 Austin Ziegler (Guest)
on 2006-02-08 15:57
(Received via mailing list)
On 08/02/06, vidar.hokstad@gmail.com <vidar.hokstad@gmail.com> wrote:
> 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.

That's what documentation is 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.

That's what documentation is for.

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

Except that contract enforcement is *expensive*, and most contracts are
much more difficult to express than can be expressed in the way that
people who are (foolishly) comforted by static typing expect.

-austin
Caf3d97ceff60d6d105c45305d34658c?d=identicon&s=25 vidar.hokstad@gmail.com (Guest)
on 2006-02-08 16:06
(Received via mailing list)
Erik Veenstra wrote:
> (Although my original post wasn't about type checking, or about
> duck-type checking. It was about how to implement such a
> wrapper...)

The generic wrapper is absolutely interesting :) It was the typing part
that triggered my interest, though, because I do prefer stricter type
checking myself.

_But_ at the same time I like the ability to selectively use it where
it matters, such as to document and enforce genuine constraints and
trigger errors as early as possible (as a consequence, I don't see that
much value in checking for a specific type, but I did like the ease of
checking for support for a specific method)

To me "ducktyping" doesn't preclude fairly strict type checking.
Haskell, for instance, for all intents and purposes provides most of
the same flexibility in terms of typing from the programmers point of
view, but still enforces typing strictly by inferring which types would
satisfy the requirements of a specific piece of code. Barring that kind
of support in the Ruby interpreter, being able to selectively and
easily wrap type check around code where the requirements are
non-obvious and hard to check is quite useful.

I like the possibilities this has for aspect oriented programming for
things like debugging and testing too - by injecting wrappers to
manipulate or check parts of the interactions in the tested code (I did
read most of RCR 321 that someone else mentioned too, but the appeal of
your suggestion is the size/simplicity of the implementation)

Vidar
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 16:49
(Received via mailing list)
Oops! A little typo...

args.each_with_index do |args, n|

....should be:

args.each_with_index do |arg, n|
Caf3d97ceff60d6d105c45305d34658c?d=identicon&s=25 vidar.hokstad@gmail.com (Guest)
on 2006-02-08 16:49
(Received via mailing list)
Austin Ziegler wrote:
> That's what documentation is for.

People don't read documentation thoroughly enough.

And even if they do, that still leaves them with a major testing
headache if artificially creating the corner cases that triggers
specific behavior from your code is hard to do.

Relying on the documentation for something that is easy to check is a
cop out. _Especially_ when it is trivial to turn that check off for
production code and remove all cost.

> Except that contract enforcement is *expensive*, and most contracts are
> much more difficult to express than can be expressed in the way that
> people who are (foolishly) comforted by static typing expect.

Many contracts are expensive yes and many aren't - I have lots of code
that depend on a single or a small set of methods to be available on an
object that is passed in. Checking it is easy. Even so, I've pointed
out that one of the things I liked about the wrapping is that it's
trivially easy to make it optional at runtime at no cost.

The wrapping approach has the advantages that:

 - The checks can be put in a separate source file and only included in
the application when you want them (or you can keep cheap checks in
critical parts of the code, and keep more expensive checks or checks on
less critical code separate and not use it in production code)
 - The checks can be trivially disabled in a such a way that there is
_no_ cost when the methods are executed (by simply switching the
wrapping off)
 - It's trivial to expand the checks without making the core code more
complex
 - It doubles as documentation that you know will be kept up to date
because things will break if it isn't. If there is one thing I NEVER
trust, it is documentation that isn't executable - it invariably gets
out of date, blatantly wrong, and outright dangerous to depend on.
 - They assist in minimising the effort of writing test code for
clients of your code, by minimising the number of potential code paths.

This has nothing to do with being "foolishly comforted by static
typing" but about 1) facilitating testing and debugging, 2) avoiding
late surprises, by failing early where possible, both of which are
good, sound practices regardless of whether you use static or dynamic
typing.

Vidar
Db59927495b7db991b543d113e1835ff?d=identicon&s=25 Maurice Codik (Guest)
on 2006-02-08 16:49
(Received via mailing list)
I dont really understand why the reaction to this is so negative--
design by
contract can be a very useful tool (even if the code presented in thir
thread is a very limited implementation of DBC).

Methods usually spend a few lines of code validating their arguments,
especially if they are intended to be used by external clients. Your
documentation should definately explain what type of arguments your
methods
expect-- and your code should make sure that its users are providing the
correct kind of arguments. You want to error ASAP if you are given a bad
argument. From a user's perspective, a "bad argument" error thrown at
the
library boundary is much easier to debug than a strange NoMethodError
thrown
deep inside your library.

Libraries like these just make argument validation a little more DRY
(ex,
define your contract once, apply it to many methods). It is not just
static
typing-- the library I wrote, for example, lets you provide Procs to
check
contracts, so you can test more dynamic/runtime properties such as "this
database connection is open."

Performance cost? If you are going to be validating the parameters to
your
methods anyway (which you should), there is very little additional
overhead.


Maurice
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 17:09
(Received via mailing list)
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... :)

   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
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 17:28
(Received via mailing list)
> I like the possibilities this has for aspect oriented
> programming for things like debugging and testing too - by
> injecting wrappers to manipulate or check parts of the
> interactions in the tested code (I did read most of RCR 321
> that someone else mentioned too, but the appeal of your
> suggestion is the size/simplicity of the implementation)

<quote src="Vidar">

 ... is the size/simplicity of the implementation

<quote>

At least *you* got my point. Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 17:44
(Received via mailing list)
>    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/
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-02-08 17:45
(Received via mailing list)
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?

http://en.wikipedia.org/wiki/Higher-order_functions

James Edward Gray II
Dd76a12d66f843de5c5f8782668e7127?d=identicon&s=25 Mauricio Fernandez (Guest)
on 2006-02-08 18:09
(Received via mailing list)
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?
>
> http://en.wikipedia.org/wiki/Higher-order_functions

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)
F3b786a23390451fa67108781b0c8fed?d=identicon&s=25 unknown (Guest)
on 2006-02-08 18:18
(Received via mailing list)
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.
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 18:19
(Received via mailing list)
> 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/
70c8da82d09d3866222976ab8978133c?d=identicon&s=25 Daniel Nugent (Guest)
on 2006-02-08 20:11
(Received via mailing list)
My beef with the specific implementation in this thread as it applies
to Design By Contract is that you're issuing these wrapper methods
individually.

A more preferential solution would involve setting up a list of
functions that need a contract applied and then being able to, in one
fell swoop, ensure that they all are bound to the contract at once,
whether that be preconditions, postconditions, or ensurances about the
parameters passed.  Maybe that'll require a couple of method calls,
but you'd still generally be doing it all at once.

I think, for me, the way we're talking about it here quacks too much
like static typing.
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-02-08 20:18
(Received via mailing list)
On Feb 8, 2006, at 1:11 PM, Daniel Nugent wrote:

> My beef with the specific implementation in this thread as it applies
> to Design By Contract is that you're issuing these wrapper methods
> individually.
>
> A more preferential solution would involve setting up a list of
> functions that need a contract applied and then being able to, in one
> fell swoop, ensure that they all are bound to the contract at once,
> whether that be preconditions, postconditions, or ensurances about the
> parameters passed.  Maybe that'll require a couple of method calls,
> but you'd still generally be doing it all at once.

class Example
   def one; ... end
   def two; ... end

   %w{one two}.each do |meth|
     apply_contract meth, ...
   end
end

James Edward Gray II
2e4e064abe3ea2a2ccc5b64acdc1ab08?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-08 21:23
(Received via mailing list)
Like this?

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

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

 require "ev/typed"

 class Module
   def my_conditions(*method_names)
     method_names.each do |method_name|
       wrap_method(method_name) do |org_method, args, block|
         # pre_conditions
         org_method.call(*args, &block)
         # post_conditions
       end
     end
   end
 end

 class Foo

  # A log of method definitions

  my_conditions :foo, :bar, :and, :friends
 end
This topic is locked and can not be replied to.