Meta-Meta-Programming

Discussion in 'Ruby' started by Erik Veenstra, Feb 7, 2006.

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

    ----------------------------------------------------------------
    Erik Veenstra, Feb 7, 2006
    #1
    1. Advertising

  2. 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")

    ----------------------------------------------------------------
    Erik Veenstra, Feb 7, 2006
    #2
    1. Advertising

  3. Erik Veenstra

    m4dc4p Guest

    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
    >
    > ----------------------------------------------------------------
    m4dc4p, Feb 7, 2006
    #3
  4. Erik Veenstra

    Eric Hodel Guest

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


    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 - - http://segment7.net
    This implementation is HODEL-HASH-9600 compliant

    http://trackmap.robotcoop.com
    Eric Hodel, Feb 7, 2006
    #4
  5. Erik Veenstra

    Lionel Thiry Guest

    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/
    Lionel Thiry, Feb 7, 2006
    #5
  6. > 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/
    Erik Veenstra, Feb 8, 2006
    #6
  7. Erik Veenstra

    Lou Vanek Guest

    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... :)
    >
    > 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
    >
    > ----------------------------------------------------------------
    Lou Vanek, Feb 8, 2006
    #7
  8. Erik Veenstra

    Trans Guest

    > 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, Feb 8, 2006
    #8
  9. ------=_Part_14829_16318285.1139360026835
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    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 =3D> lambda {|x| x.respond_to?("write") and
    x.respond_to?("closed?") and not x.closed? },
    :positive =3D> lambda {|x| x >=3D 0 }

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

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

    tc =3D 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.r=
    b

    Maurice

    On 2/7/06, Trans <> wrote:
    >
    > > 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.
    >
    >
    >


    ------=_Part_14829_16318285.1139360026835--
    Maurice Codik, Feb 8, 2006
    #9
  10. Erik Veenstra

    Trans Guest

    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.
    Trans, Feb 8, 2006
    #10
  11. Erik Veenstra

    Guest

    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
    , Feb 8, 2006
    #11
  12. 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/
    Erik Veenstra, Feb 8, 2006
    #12
  13. 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/
    Erik Veenstra, Feb 8, 2006
    #13
  14. 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 <> 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
    Daniel Nugent, Feb 8, 2006
    #14
  15. Erik Veenstra

    Guest

    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
    , Feb 8, 2006
    #15
  16. On 08/02/06, <> wrote:
    > 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.


    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
    --
    Austin Ziegler *
    * Alternate:
    Austin Ziegler, Feb 8, 2006
    #16
  17. Erik Veenstra

    Guest

    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
    , Feb 8, 2006
    #17
  18. Oops! A little typo...

    args.each_with_index do |args, n|

    ....should be:

    args.each_with_index do |arg, n|
    Erik Veenstra, Feb 8, 2006
    #18
  19. Erik Veenstra

    Guest

    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
    , Feb 8, 2006
    #19
  20. ------=_Part_3371_33354180.1139413432008
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    I dont really understand why the reaction to this is so negative-- design b=
    y
    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 throw=
    n
    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 Codik, Feb 8, 2006
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Nym Pseudo

    META NAME and META HTTP-EQUIV

    Nym Pseudo, Sep 26, 2003, in forum: HTML
    Replies:
    1
    Views:
    525
    =?iso-8859-1?Q?brucie?=
    Sep 26, 2003
  2. Bruce Dickey

    Meta programming question

    Bruce Dickey, Sep 28, 2003, in forum: Python
    Replies:
    8
    Views:
    332
    Bruce Dickey
    Sep 29, 2003
  3. Jon Slaughter

    C++ Meta Programming language

    Jon Slaughter, Jul 2, 2005, in forum: C++
    Replies:
    27
    Views:
    821
    Ira Baxter
    Jul 24, 2005
  4. Duane Johnson

    Meta methods to govern meta data?

    Duane Johnson, Oct 25, 2005, in forum: Ruby
    Replies:
    6
    Views:
    217
    Adam Sanderson
    Oct 28, 2005
  5. Erik Veenstra

    Meta-Meta-Programming, revisited

    Erik Veenstra, Jul 21, 2006, in forum: Ruby
    Replies:
    21
    Views:
    415
    Erik Veenstra
    Jul 25, 2006
Loading...

Share This Page