![]() |
Meta-Meta-Programming
I had a discussion with a friend. A Java guy. He wants the arguments of a method call being checked. "I want the first one to be an Integer. And the second one is a String. Period." No discussion. I explained our duck-typing paradigm. He's not convinced. He thinks Java. So, he gets Java. Lets check the types of the arguments of a method call! (This post is not about type checking at all. It's about how to implement such a type checker. Or, more general, it's about monitoring-functions.) I wanted to do this with a nice and clean implementation, with the real magic pushed down to a place I would never come again ("write once, read never"). I wanted something like this (focus on line 7): 1 class Foo 2 def bar(x, y, z) 3 # x should be Numeric 4 # y should be a String 5 # z should respond to :to_s 6 end 7 typed :bar, Numeric, String, :to_s # !!!!! 8 end Focus on line 7, once again. Make it three times. It's all about line 7. That was good enough for him. "But you can't do this. You simply can't. That's magic." I laughed at him, turned around and did it... That's where this story is all about... First, I'll give you a piece of code which doesn't do anything, except that it seems to wrap the original method in another method (focus on line 12): 1 class Module 2 def just_wrap(method_name) 3 wrap_method(method_name) do |org_method, args, block| 4 org_method.call(*args, &block) 5 end 6 end 7 end 8 class Foo 9 def bar(x, y, z) 10 p [x, y, z] 11 end 12 just_wrap :bar # !!!!! 13 end 14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"] You can find the implementation of wrap_method below. This thread is all about that very one method. It's the big trick. You don't need to understand its implementation. Knowing how to use it is good enough. Line 3 retrieves the original method and yields the given block with this method, as well as with its arguments and block. Not *args, not &block. Just args and block. Blocks don't get blocks, you know. (Although it's introduced in Ruby 1.9.) Within the given block, we can do whatever we want to. That's where the real stuff goes. But, someday, we have to call the original method with the original parameters and the original block. That's what we do on line 4. That's about it. That's the whole story. There's nothing more to say. Except for an example or two... Here's a simple example. It "upcases" every argument. It must be silly to "upcase" every argument like this, but we'll do it anyway. Introducing line 4: 1 class Module 2 def big_arguments(method_name) 3 wrap_method(method_name) do |org_method, args, block| 4 args = args.collect{|x| x.to_s.upcase} 5 org_method.call(*args, &block) 6 end 7 end 8 end 9 class Foo 10 def bar(x, y, z) 11 [x, y, z] 12 end 13 big_arguments :bar 14 end 15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"] Here's another example. Lines 4, 5 and 6. They inform you about nil things. 1 class Module 2 def find_nil(method_name) 3 wrap_method(method_name) do |org_method, args, block| 4 if args.include?(nil) 5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}." 6 end 7 org_method.call(*args, &block) 8 end 9 end 10 end 11 class Foo 12 def bar(x, y, z) 13 end 14 find_nil :bar 15 end 16 Foo.new.bar("a", "b", "c") # ===> 17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"]. 18 Foo.new.bar("a", "b", "c") # ===> I call "typed", "just_wrap", "big_arguments" and "find_nil": monitor-functions. I don't know exactly how this term got into my head, but it does sound good: monitor-functions. It's definitely better than wrap-method-functions. (You can build non-monitor-functions as well. But that's really stupid: monitor-and-non-monitor-functions.) Meanwhile, I played with a couple of monitor-functions: debugging, logging, synchronization, statistics, benchmarking, roles (like on WebSphere). Ideas? It's easy to create them. Try it. Let me know. Forget about the implementation of "wrap_method". It's just sitting there, waiting to be used to implement a monitor-function. It's easy to implement a monitor-function. And it's very, very easy to use it. Those where my goals. Oh, by the way, if such a monitor-function is kind of meta-programming (it's a buzz-word, I know, but it is, isn't it?), how would you call "wrap_method"? Meta-meta-programming? It was just an idea. Just wanted to tell you. Couldn't sleep. Feel much better now. Maybe I can sleep... Thanks for listening. gegroet, Erik V. - http://www.erikveen.dds.nl/ PS: Sorry for this rather lengthy post. It just got a bit lengthier than I planned. It just happened. No control. ---------------------------------------------------------------- class Module # With this, we can create monitoring functions. # It might not be clearly readable, # but it's written only once. # Write once, read never. # Forget about the internals. # Just use it. # It should be part of Ruby itself, anyway... :) 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 ---------------------------------------------------------------- |
Re: Meta-Meta-Programming
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") ---------------------------------------------------------------- |
Re: Meta-Meta-Programming
Slick!
Erik Veenstra wrote: > I had a discussion with a friend. A Java guy. He wants the > arguments of a method call being checked. "I want the first one > to be an Integer. And the second one is a String. Period." No > discussion. I explained our duck-typing paradigm. He's not > convinced. He thinks Java. So, he gets Java. > > Lets check the types of the arguments of a method call! > > (This post is not about type checking at all. It's about how to > implement such a type checker. Or, more general, it's about > monitoring-functions.) > > I wanted to do this with a nice and clean implementation, with > the real magic pushed down to a place I would never come again > ("write once, read never"). I wanted something like this (focus > on line 7): > > 1 class Foo > 2 def bar(x, y, z) > 3 # x should be Numeric > 4 # y should be a String > 5 # z should respond to :to_s > 6 end > 7 typed :bar, Numeric, String, :to_s # !!!!! > 8 end > > Focus on line 7, once again. Make it three times. It's all > about line 7. > > That was good enough for him. "But you can't do this. You > simply can't. That's magic." I laughed at him, turned around > and did it... > > That's where this story is all about... > > First, I'll give you a piece of code which doesn't do anything, > except that it seems to wrap the original method in another > method (focus on line 12): > > 1 class Module > 2 def just_wrap(method_name) > 3 wrap_method(method_name) do |org_method, args, block| > 4 org_method.call(*args, &block) > 5 end > 6 end > 7 end > 8 class Foo > 9 def bar(x, y, z) > 10 p [x, y, z] > 11 end > 12 just_wrap :bar # !!!!! > 13 end > 14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"] > > You can find the implementation of wrap_method below. This > thread is all about that very one method. It's the big trick. > You don't need to understand its implementation. Knowing how to > use it is good enough. > > Line 3 retrieves the original method and yields the given block > with this method, as well as with its arguments and block. Not > *args, not &block. Just args and block. Blocks don't get > blocks, you know. (Although it's introduced in Ruby 1.9.) > > Within the given block, we can do whatever we want to. That's > where the real stuff goes. > > But, someday, we have to call the original method with the > original parameters and the original block. That's what we do > on line 4. > > That's about it. That's the whole story. There's nothing more > to say. > > Except for an example or two... > > Here's a simple example. It "upcases" every argument. It must > be silly to "upcase" every argument like this, but we'll do it > anyway. Introducing line 4: > > 1 class Module > 2 def big_arguments(method_name) > 3 wrap_method(method_name) do |org_method, args, block| > 4 args = args.collect{|x| x.to_s.upcase} > 5 org_method.call(*args, &block) > 6 end > 7 end > 8 end > 9 class Foo > 10 def bar(x, y, z) > 11 [x, y, z] > 12 end > 13 big_arguments :bar > 14 end > 15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"] > > Here's another example. Lines 4, 5 and 6. They inform you about > nil things. > > 1 class Module > 2 def find_nil(method_name) > 3 wrap_method(method_name) do |org_method, args, block| > 4 if args.include?(nil) > 5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}." > 6 end > 7 org_method.call(*args, &block) > 8 end > 9 end > 10 end > 11 class Foo > 12 def bar(x, y, z) > 13 end > 14 find_nil :bar > 15 end > 16 Foo.new.bar("a", "b", "c") # ===> > 17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"]. > 18 Foo.new.bar("a", "b", "c") # ===> > > I call "typed", "just_wrap", "big_arguments" and "find_nil": > monitor-functions. I don't know exactly how this term got into > my head, but it does sound good: monitor-functions. It's > definitely better than wrap-method-functions. (You can build > non-monitor-functions as well. But that's really stupid: > monitor-and-non-monitor-functions.) > > Meanwhile, I played with a couple of monitor-functions: > debugging, logging, synchronization, statistics, benchmarking, > roles (like on WebSphere). Ideas? It's easy to create them. Try > it. Let me know. > > Forget about the implementation of "wrap_method". It's just > sitting there, waiting to be used to implement a > monitor-function. It's easy to implement a monitor-function. > And it's very, very easy to use it. Those where my goals. > > Oh, by the way, if such a monitor-function is kind of > meta-programming (it's a buzz-word, I know, but it is, isn't > it?), how would you call "wrap_method"? Meta-meta-programming? > > It was just an idea. Just wanted to tell you. Couldn't sleep. > Feel much better now. Maybe I can sleep... > > Thanks for listening. > > gegroet, > Erik V. - http://www.erikveen.dds.nl/ > > PS: Sorry for this rather lengthy post. It just got a bit > lengthier than I planned. It just happened. No control. > > ---------------------------------------------------------------- > > class Module > > # With this, we can create monitoring functions. > # It might not be clearly readable, > # but it's written only once. > # Write once, read never. > # Forget about the internals. > # Just use it. > # It should be part of Ruby itself, anyway... :) > > def wrap_method(method_name, *args1, &block1) > @_wrap_method_count_ ||= 0 > @_wrap_method_count_ += 1 > > prefix = "_wrap_method_#{@_wrap_method_count_}" > > module_eval <<-EOF > alias :#{prefix}_org :#{method_name} # Store the original method for later use. > > define_method(:#{prefix}_args) {args1} # Store the arguments of the call to Module#wrap_method. (Not used.) > define_method(:#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method. > > def #{method_name}(*args2, &block2) > #{prefix}_block.call(method(:#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2! > end > EOF > end > > end > > ---------------------------------------------------------------- |
Re: Meta-Meta-Programming
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 - drbrain@segment7.net - http://segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com |
Re: Meta-Meta-Programming
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/ |
Re: Meta-Meta-Programming
> 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/ |
Re: Meta-Meta-Programming
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 > > ---------------------------------------------------------------- |
Re: Meta-Meta-Programming
> 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. |
Re: Meta-Meta-Programming
------=_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 <transfire@gmail.com> 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-- |
Re: Meta-Meta-Programming
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. |
| All times are GMT. The time now is 05:21 AM. |
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.