Meta-Meta-Programming, revisited

Discussion in 'Ruby' started by Erik Veenstra, Jul 21, 2006.

  1. Do you remember the discussion about monitor-functions and
    metameta-programming?

    Well, I've completely rewritten this Module#wrap_method. It
    should be more robust now: thread-safety, better execution
    order of recursively wrapped methods, better execution order of
    method-is-defined-in-superclass, etc.

    I added some convenience methods as well: Module#pre_condition
    and Module#post_condition. These are really easy to use!

    The article and the code are here:

    http://www.erikveen.dds.nl/monitorfunctions/index.html

    Please, read it, both the article and the implementation,
    especially the implementation, read it again, think about it,
    test it, analyze it, use it and shoot.

    But do not benchmark it... ;]

    Thanks.

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

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

    EXCERPT:

    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!

    (Well, this article 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 8):

    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
    8 check_types :bar, Numeric, String, :to_s
    9 end

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

    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. To be more accurate: It's
    about how I did it, about monitor-functions and
    method-wrapping, not about type-checking.

    ----------------------------------------------------------------
     
    Erik Veenstra, Jul 21, 2006
    #1
    1. Advertising

  2. On 7/21/06, Erik Veenstra <> wrote:
    > Do you remember the discussion about monitor-functions and
    > metameta-programming?
    >
    > Well, I've completely rewritten this Module#wrap_method. It
    > should be more robust now: thread-safety, better execution
    > order of recursively wrapped methods, better execution order of
    > method-is-defined-in-superclass, etc.


    I just read your paper and it seems very interesting. Please, see my
    comments below.

    > I added some convenience methods as well: Module#pre_condition
    > and Module#post_condition. These are really easy to use!


    IMHO these functions need to be renamed. Those names remind me of DBC
    and do not fully reflect what they do. Moreover, some kind of DBC
    could be implemented using this module and hence you'd have a name
    clash.

    Good work!

    Ed
    --=20
    Encontr=E1 a "Tu psic=F3pata favorito" http://tuxmaniac.blogspot.com

    Thou shalt study thy libraries and strive not to reinvent them without caus=
    e,
    that thy code may be short and readable and thy days pleasant and productiv=
    e.
    -- Seventh commandment for C programmers

    I have made this letter longer than usual because I lack the time to
    make it shorter.
    -- Blaise Pascal
     
    Edgardo Hames, Jul 21, 2006
    #2
    1. Advertising

  3. > > Do you remember the discussion about monitor-functions and
    > > metameta-programming?
    > >
    > > Well, I've completely rewritten this Module#wrap_method. It
    > > should be more robust now: thread-safety, better execution
    > > order of recursively wrapped methods, better execution
    > > order of method-is-defined-in-superclass, etc.


    I'm still fighting the method-is-defined-in-module situation...

    > > I added some convenience methods as well:
    > > Module#pre_condition and Module#post_condition. These are
    > > really easy to use!

    >
    > IMHO these functions need to be renamed. Those names remind
    > me of DBC and do not fully reflect what they do. Moreover,
    > some kind of DBC could be implemented using this module and
    > hence you'd have a name clash.


    I'll alias them... ;] (pre_action and post_action?)

    > Good work!


    Meanwhile, I use it all over: logging, counting, caching,
    statistics. It's all done without touching the original code.
    Feels good...

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/
     
    Erik Veenstra, Jul 22, 2006
    #3
  4. Erik Veenstra

    Guest

    Hi Erik,

    Nice write-up. A couple of thoughts...

    I think pre_ and post_condition is a bit, um... non-Rubyish, I guess is
    the best way to put it. I don't think the reciever of the wrap should
    be an argument. Instead just let it be ther reciever of the pre_ call.
    For example Instead of:

    def def_types(*types)
    pre_condition(Module, :method_added) do |*args|
    ...

    try

    def def_types(*types)
    Module.pre_condition:)method_added) do |*args|
    ...

    Yes, that would mean the pre_ methods are public, but does it matter
    since that's what you're doing anyway. Of course you could always use
    #send too.

    Also, #wrap_method seems like it could do with some simplification.
    Taking that to the furthest case, is their a reason the the following
    definition isn't enough?

    def wrap_method( sym, &blk )
    raise ArgumentError, "method does not exist" unless
    method_defined?( sym )
    old = instance_method(sym)
    define_method(sym) { |*args| blk.call(old.bind(self), *args) }
    end

    Thanks,
    T.
     
    , Jul 22, 2006
    #4
  5. > I don't think the reciever of the wrap should be an argument.
    > Instead just let it be ther reciever of the pre_ call. For
    > example Instead of:
    >
    > pre_condition(Module, :method_added) do |*args|
    >
    > Module.pre_condition:)method_added) do |*args| # NOT THE SAME


    Module is not the receiver! The argument Module is just an
    indicator (stupid abuse), so pre_condition knows that is has to
    wrap a module method (with "class << self" in
    wrap_module_method) instead of an instance method.

    I should have called it pre_module_condition. (Like
    wrap_module_method instead of wrap_method.) I was just sick of
    creating more methods... ;]

    > Also, #wrap_method seems like it could do with some
    > simplification. Taking that to the furthest case, is their a
    > reason the the following definition isn't enough?
    >
    > def wrap_method( sym, &blk )
    > raise ArgumentError, "method does not exist" unless method_defined?( sym )
    > old = instance_method(sym)
    > define_method(sym) { |*args| blk.call(old.bind(self), *args) }
    > end


    Well, life isn't that easy... (Have a look at the code below.)
    First thoughts:

    * You definitely want to pass the block from the original
    invocation to the original definition... Really... (In Ruby
    1.9, this could be done your way, since blocks do get blocks.
    But not in Ruby 1.8.)

    * I wanted to be able to wrap non-existing methods.

    * When wrapping :initialize in Bar, your wrap_method complains
    with "method does not exist".

    * When wrapping methods in both the class and the superclass,
    going up and down, you can't (at wrap-time) determine the
    order in which the blocks are to be executed (at run-time).
    If I run the code below with your wrap_method (after removing
    the "method does not exist" check), this :wrap_Foo is gone!
    It's not what _I_ expected... ;] (See [1] for a more
    complicated example. I'll try to make a diagram.)

    It's complicated stuff... I tried to hide this complexity for
    the user, by providing a clean interface (wrap_method) and an
    even cleaner interface (pre_condition). I hope you find them
    easy to use.

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

    [1] http://www.erikveen.dds.nl/monitorfunctions/index.html#5.1.0

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

    class Foo
    def initialize(&block)
    p [:initialize, block]
    end
    end

    class Bar < Foo
    wrap_method:)initialize) do |m, *a|
    p [:wrap_Bar]

    m.call(*a)
    end
    end

    class Foo
    wrap_method:)initialize) do |m, *a|
    p [:wrap_Foo]

    m.call(*a)
    end
    end

    Bar.new{}

    ----------------------------------------------------------------
     
    Erik Veenstra, Jul 22, 2006
    #5
  6. Erik Veenstra

    Guest

    Erik Veenstra wrote:
    > > I don't think the reciever of the wrap should be an argument.
    > > Instead just let it be ther reciever of the pre_ call. For
    > > example Instead of:
    > >
    > > pre_condition(Module, :method_added) do |*args|
    > >
    > > Module.pre_condition:)method_added) do |*args| # NOT THE SAME

    >
    > Module is not the receiver! The argument Module is just an
    > indicator (stupid abuse), so pre_condition knows that is has to
    > wrap a module method (with "class << self" in
    > wrap_module_method) instead of an instance method.
    >
    > I should have called it pre_module_condition. (Like
    > wrap_module_method instead of wrap_method.) I was just sick of
    > creating more methods... ;]


    Sorry, I misprepresnted what I meant. Use "aModule" instead of
    "Module". -- You created a special method: #wrap_module_method for
    doing #wrap_method external to the a module/class, but you could just
    make it public (or use send):

    aModule.wrap_method

    And likewise

    aModule.pre_condition
    aModule.post_condition

    > > Also, #wrap_method seems like it could do with some
    > > simplification. Taking that to the furthest case, is their a
    > > reason the the following definition isn't enough?
    > >
    > > def wrap_method( sym, &blk )
    > > raise ArgumentError, "method does not exist" unless method_defined?( sym )
    > > old = instance_method(sym)
    > > define_method(sym) { |*args| blk.call(old.bind(self), *args) }
    > > end

    >
    > Well, life isn't that easy... (Have a look at the code below.)
    > First thoughts:
    >
    > * You definitely want to pass the block from the original
    > invocation to the original definition... Really... (In Ruby
    > 1.9, this could be done your way, since blocks do get blocks.
    > But not in Ruby 1.8.)
    >
    > * I wanted to be able to wrap non-existing methods.
    >
    > * When wrapping :initialize in Bar, your wrap_method complains
    > with "method does not exist".


    I see. I'm recall considering this before. Obviously I had gone the
    other direction, becuase then hwy use def at all, alwasy use
    #wrap_method (closure issues not with standing). That reminds me of an
    older notaiotn of mine where 'def' itself would actually wrap a
    prexisting method automatically and you'd use #super to call it. But I
    digress...

    > * When wrapping methods in both the class and the superclass,
    > going up and down, you can't (at wrap-time) determine the
    > order in which the blocks are to be executed (at run-time).
    > If I run the code below with your wrap_method (after removing
    > the "method does not exist" check), this :wrap_Foo is gone!
    > It's not what _I_ expected... ;] (See [1] for a more
    > complicated example. I'll try to make a diagram.)


    Owww! Nice catch. I hadn't though of that.

    > It's complicated stuff... I tried to hide this complexity for
    > the user, by providing a clean interface (wrap_method) and an
    > even cleaner interface (pre_condition). I hope you find them
    > easy to use.


    Indeed. I'm going to work with this see what I can incoprate into
    Facets', if that's cool with you.

    Thanks,
    T.
     
    , Jul 22, 2006
    #6
  7. > Sorry, I misprepresnted what I meant. Use "aModule" instead
    > of "Module". -- You created a special method:
    > #wrap_module_method for doing #wrap_method external to the a
    > module/class, but you could just make it public (or use
    > send):
    >
    > aModule.wrap_method


    Wrong!... ;] The receiver of wrap_method is *always* a
    module/class. When wrapping an instance method, with
    wrap_method, the receiver is a module. When wrapping a module
    method, with wrap_module_method, the receiver is the singleton
    class of that module.

    So, you should write aModule.meta_class.wrap_method (using
    Why's little meta library...). Or, in default Ruby:

    class << aModule
    self
    end.instance_eval
    wrap_method(method_name){...}
    end

    I don't want to see this in regular, every day Ruby code.
    That's why I came up with this wrap_module_method and this
    stupid Module argument thing. ;] Kind of shortcuts...

    > ...becuase then hwy use def at all, alwasy use
    > #wrap_method...


    ;]

    > > * When wrapping methods in both the class and the superclass,
    > > going up and down, you can't (at wrap-time) determine the
    > > order in which the blocks are to be executed (at run-time).
    > > If I run the code below with your wrap_method (after removing
    > > the "method does not exist" check), this :wrap_Foo is gone!
    > > It's not what _I_ expected... ;] (See [1] for a more
    > > complicated example. I'll try to make a diagram.)

    >
    > Owww! Nice catch. I hadn't though of that.


    ;]

    > > It's complicated stuff... I tried to hide this complexity
    > > for the user, by providing a clean interface (wrap_method)
    > > and an even cleaner interface (pre_condition). I hope you
    > > find them easy to use.

    >
    > Indeed. I'm going to work with this see what I can incoprate
    > into Facets', if that's cool with you.


    Sure. Everybody is free to copy the code and use it for
    whatever reason. If you make money with it, please call me...

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/
     
    Erik Veenstra, Jul 22, 2006
    #7
  8. Erik Veenstra

    Guest

    Erik Veenstra wrote:
    > > Sorry, I misprepresnted what I meant. Use "aModule" instead
    > > of "Module". -- You created a special method:
    > > #wrap_module_method for doing #wrap_method external to the a
    > > module/class, but you could just make it public (or use
    > > send):
    > >
    > > aModule.wrap_method

    >
    > Wrong!... ;] The receiver of wrap_method is *always* a
    > module/class. When wrapping an instance method, with
    > wrap_method, the receiver is a module. When wrapping a module
    > method, with wrap_module_method, the receiver is the singleton
    > class of that module.
    >
    > So, you should write aModule.meta_class.wrap_method (using
    > Why's little meta library...). Or, in default Ruby:
    >
    > class << aModule
    > self
    > end.instance_eval
    > wrap_method(method_name){...}
    > end
    >
    > I don't want to see this in regular, every day Ruby code.
    > That's why I came up with this wrap_module_method and this
    > stupid Module argument thing. ;] Kind of shortcuts...


    Well, that's the thing. I'd rather do

    aModule.meta.wrap_method
    aModule.meta.pre_condition

    Then have all these different useage forms:

    wrap_method
    wrap_module_method
    pre_condition( aModule,

    T.

    > > > * When wrapping methods in both the class and the superclass,
    > > > going up and down, you can't (at wrap-time) determine the
    > > > order in which the blocks are to be executed (at run-time).
    > > > If I run the code below with your wrap_method (after removing
    > > > the "method does not exist" check), this :wrap_Foo is gone!
    > > > It's not what _I_ expected... ;] (See [1] for a more
    > > > complicated example. I'll try to make a diagram.)

    > >
    > > Owww! Nice catch. I hadn't though of that.

    >
    > ;]


    On looking at it again, I think it goes back to you wnating to wrap
    methods that aren;t there. When subclass, there's not much use in
    wrapping when one can just define the method and call super.

    > Sure. Everybody is free to copy the code and use it for
    > whatever reason. If you make money with it, please call me...


    Money? Don't make it.

    T.
     
    , Jul 22, 2006
    #8
  9. > > So, you should write aModule.meta_class.wrap_method (using
    > > Why's little meta library...).

    >
    > Well, that's the thing. I'd rather do
    >
    > aModule.meta.wrap_method
    > aModule.meta.pre_condition


    I agree. I added it to my (not-yet-published) version.

    What about the abuse of Array and Object, as arguments?

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/
     
    Erik Veenstra, Jul 22, 2006
    #9
  10. Erik Veenstra

    Guest

    Erik Veenstra wrote:
    > > > So, you should write aModule.meta_class.wrap_method (using
    > > > Why's little meta library...).

    > >
    > > Well, that's the thing. I'd rather do
    > >
    > > aModule.meta.wrap_method
    > > aModule.meta.pre_condition

    >
    > I agree. I added it to my (not-yet-published) version.
    >
    > What about the abuse of Array and Object, as arguments?


    I'm not sure the Array abuse is needed if you limit the arguement to
    the array itself, eg. don;t splat it whencalling the block. The reason
    is that block unlik lambdas are more flexiable with array argument and
    cna automatically splt them. Then you can just use #replace tpo change
    the args if you want:

    $a = ["a", "b", "c"]

    def c(&block)
    block.call( $a )
    end

    c { |a| p a.replace([2,3]) }

    $a #=> [2, 3]

    But also,

    c { |a,b| p a+b } #=> 5

    I'll have to look at the Object abuse again and get back to you.

    T.
     
    , Jul 22, 2006
    #10
  11. Erik Veenstra, Jul 23, 2006
    #11
  12. > I'm not sure the Array abuse is needed if you limit the
    > arguement to the array itself, eg. don;t splat it whencalling
    > the block


    True. I did that before. But receiving |*args| in blocks is so
    common (for me), that I was typing it again and again,
    introducing bug after bug. That's why I introduced this
    Array-parameter-thing.

    > I'll have to look at the Object abuse again and get back to
    > you.


    Please.

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/
     
    Erik Veenstra, Jul 23, 2006
    #12
  13. Erik Veenstra

    Trans Guest

    Erik Veenstra wrote:
    > > I'm not sure the Array abuse is needed if you limit the
    > > arguement to the array itself, eg. don;t splat it whencalling
    > > the block

    >
    > True. I did that before. But receiving |*args| in blocks is so
    > common (for me), that I was typing it again and again,
    > introducing bug after bug. That's why I introduced this
    > Array-parameter-thing.


    I see. But |*args| does work just so long as you don't want to _change
    the args_, doesn't it?

    > > I'll have to look at the Object abuse again and get back to
    > > you.

    >
    > Please.


    Okay. Looking at your "Object" example:

    require "ostruct"
    class Foo < Struct.new:)aa, :bbb)
    post_condition:)initialize, Object) do
    line = caller.select{|s|
    s.include?(__FILE__)}.shift.scan(/\d+/)[-1]

    puts "An object of class #{self.class} has been created."
    puts "The arguments are: @aa=#{aa} and @bbb=#{bbb}."
    puts "The object has id: #{__id__}."
    puts "It's done on line: #{line}."
    puts
    end
    end

    So you're confining the context of excution, shutting out the defining
    closure. Correct? It seems reasonable, though couldn't one do that on
    their own if they wanted? Eg.

    class Foo < Struct.new:)aa, :bbb)
    post_condition:)initialize) do
    instance_eval do
    line = caller.select{|s|
    s.include?(__FILE__)}.shift.scan(/\d+/)[-1]

    puts "An object of class #{self.class} has been created."
    puts "The arguments are: @aa=#{aa} and @bbb=#{bbb}."
    puts "The object has id: #{__id__}."
    puts "It's done on line: #{line}."
    puts
    end
    end
    end

    These parameter "abuses" certainly make particular uses more succinct,
    but on the downside, they are more syntax to learn, as opposed to being
    able to draw upon what one already knows about Ruby.

    T.
     
    Trans, Jul 23, 2006
    #13
  14. > When wrapping methods in both the class and the superclass,
    > going up and down, you can't (at wrap-time) determine the
    > order in which the blocks are to be executed (at run-time).
    > If I run the code below with your wrap_method (after removing
    > the "method does not exist" check), this :wrap_Foo is gone!
    > It's not what _I_ expected... ;] (See [1] for a more
    > complicated example. I'll try to make a diagram.)


    I've added the diagram [1]... ;]

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

    [1] file://localhost/home/erik/web/monitorfunctions/index.html#5.1.0
     
    Erik Veenstra, Jul 23, 2006
    #14
  15. Rimantas Liubertas, Jul 23, 2006
    #15
  16. > I see. But |*args| does work just so long as you don't want
    > to _change the args_, doesn't it?


    It's possible (not tested) to change the members of *args, but
    not *args itself.

    > > > I'll have to look at the Object abuse again and get back
    > > > to you.

    >
    > So you're confining the context of excution, shutting out the
    > defining closure. Correct? It seems reasonable, though
    > couldn't one do that on their own if they wanted? Eg.
    >
    > class Foo < Struct.new:)aa, :bbb)
    > post_condition:)initialize) do
    > instance_eval do


    Doesn't work. You should have passed the object (the receiver
    of instance_eval) to the block. Which doesn't work nicely in
    combination with *args...

    > These parameter "abuses" certainly make particular uses more
    > succinct, but on the downside, they are more syntax to learn,
    > as opposed to being able to draw upon what one already knows
    > about Ruby.


    True. There's a lot of context switching below the surface.
    wrap_method and *_condition should take care of that. By
    default, the given block should behave as expected: It has its
    own context, as in plain Ruby. But you _could_ execute it in
    the context of the object (by passing Object).

    We can get rid of the abuse of Array, but it's just more common
    to see |*args| then |args|.

    What about this?: We could remove both abuses by passing both
    the arguments and the object: |args, obj|. Or even this: |args,
    block, obj|. (Which drags block into the game...)

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/
     
    Erik Veenstra, Jul 23, 2006
    #16
  17. Erik Veenstra, Jul 23, 2006
    #17
  18. Erik Veenstra

    7rans Guest

    Erik Veenstra wrote:

    > What about this?: We could remove both abuses by passing both
    > the arguments and the object: |args, obj|. Or even this: |args,
    > block, obj|. (Which drags block into the game...)


    More flexible and less "special". Seems like a good idea. Like the
    diagram btw.

    T.
     
    7rans, Jul 25, 2006
    #18
  19. > More flexible and less "special". Seems like a good idea.

    What about |obj, method_name, args, block|? That one must be
    easy to remember, since it resembles the order of the call
    (object is the receiver).

    > Like the diagram btw.


    ;]

    Last night, I glanced over the front page of the programming
    section of Reddit, like I do every night. There it was: "Ruby
    Monitor-Functions, Or Meta-Meta-Programming in Ruby"! Very
    funny... ;]

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

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

    post_condition:)description) do |obj, method_name, args, block|
    obj.instance_eval do
    puts @text
    end
    end

    ----------------------------------------------------------------
     
    Erik Veenstra, Jul 25, 2006
    #19
  20. Erik Veenstra

    7rans Guest

    Erik Veenstra wrote:
    > > More flexible and less "special". Seems like a good idea.

    >
    > What about |obj, method_name, args, block|? That one must be
    > easy to remember, since it resembles the order of the call
    > (object is the receiver).
    >
    > > Like the diagram btw.

    >
    > ;]
    >
    > Last night, I glanced over the front page of the programming
    > section of Reddit, like I do every night. There it was: "Ruby
    > Monitor-Functions, Or Meta-Meta-Programming in Ruby"! Very
    > funny... ;]
    >
    > gegroet,
    > Erik V. - http://www.erikveen.dds.nl/
    >
    > ----------------------------------------------------------------
    >
    > post_condition:)description) do |obj, method_name, args, block|
    > obj.instance_eval do
    > puts @text
    > end
    > end
    >
    > ----------------------------------------------------------------


    Very nice.

    Not sure how the method_name parameter is useful, isn't it just
    :description? In any case, the paraemters are nicely intuitive!

    T.
     
    7rans, Jul 25, 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:
    566
    =?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:
    348
    Bruce Dickey
    Sep 29, 2003
  3. Jon Slaughter

    C++ Meta Programming language

    Jon Slaughter, Jul 2, 2005, in forum: C++
    Replies:
    27
    Views:
    846
    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:
    246
    Adam Sanderson
    Oct 28, 2005
  5. Erik Veenstra

    Meta-Meta-Programming

    Erik Veenstra, Feb 7, 2006, in forum: Ruby
    Replies:
    29
    Views:
    399
    Erik Veenstra
    Feb 8, 2006
Loading...

Share This Page