Shinyshell Community Forums > Coding > Ruby Metaprogramming |
||||||
Sign Up | Member List | New Posts | Help |
[1]
September 07 02009, 03:37 GMT | ||||
SpaceMan Member Location: Earth Post count: 32 |
Metaprogramming lets you to generate code. In C/C++, there are macros, in Python, there are decorators, in Ruby, you can call functions inside class definitions. This lets you to generate special method. For example attr_accessor is a method generator. It generates the getters and setters for attributes.
Metaprogramming can change a general-propose programming language to a domain-specific language. You can perform unit calculation in Ruby: 10.m + 5.cm # 10.05 It can be done in the following code: class Numeric def cm; self * 0.01; end def m; self; end # ...... end You can make a shortcut for "def ...; self * ...; end", it can be achieved with metaprogramming. class Module def unit(name, x) class_eval "def #{name}; self * #{x}; end;" end end By doing that, you added a new method to Module: unit. Now you can do this instead of the defs. class Numeric unit :cm, 0.01 unit :m, 1 # etc. end You can even add type-checking to methods. class Module def type_checked(name, *types, &block) class_eval do define_method(name) do |*args| i = 0 if args.length < types.length raise ArgumentError.new "Not enough number of arguments. Excepting #{types.length}. Got #{args.length}" end types.each do |type| if not args.kind_of? type raise TypeError.new "Excepting #{type} for paramter #{i}. Got #{args.class}." end i += 1 end block.call(*args) end end end end To create a type-checked method: type_checked :add, Fixnum, Fixnum do |x, y| x + y end It will make add("hello", "world") throw an exception. The CGI module lets you to do this, so it will be more convenient to generate HTML code: cgi = new CGI 'html4' cgi.out { cgi.html { cgi.body { cgi.p { } } } } The CGI module first created an array of all HTML tags, then it uses class_eval to add all methods named by the HTML tags. for element in %w # list of all tags methods += <<-BEGIN + nn_element_def(element) + <<-END # define the method based on the tag def #{element.downcase}(attributes = {}) BEGIN end END end I wrote an example of Ruby metaprogramming, #!/usr/bin/env ruby class Module def unit(name, x) class_eval "def #{name}; self * #{x}; end;" end def print_calls(name, &block) class_eval do define_method(name) do |*args| before = Time.new puts " #{before}: Entering method #{self.class}::#{name}. Params: (#{args.join(', ')})" block.call(*args) after = Time.new puts " #{after}: Exiting method #{self.class}::#{name}. Time elapsed: #{after - before} seconds" end end end def type_checked(name, *types, &block) class_eval do define_method(name) do |*args| i = 0 if args.length < types.length raise ArgumentError.new "Not enough number of arguments. Excepting #{types.length}. Got #{args.length}" end types.each do |type| if not args.kind_of? type raise TypeError.new "Excepting #{type} for paramter #{i}. Got #{args.class}." end i += 1 end block.call(*args) end end end end class Numeric unit :mm, 0.001 unit :cm, 0.01 unit :dm, 0.1 unit :m, 1 unit :km, 1000 unit :miliseconds, 0.001 unit :seconds, 1 unit :minutes, 60 unit :hours, 3600 unit :days, 24.hours unit :weeks, 7.days unit :months, 30.days unit :years, 365.days end class MethodGeneratorTest print_calls(:hello) do |name| puts "Hello, #{name}!" end type_checked :add, Fixnum, Fixnum do |x, y| x + y end end puts "Content-Type: text/plain" puts "" puts "10cm + 28mm + 2dm + 4m = #{10.cm + 28.mm + 2.dm + 4.m}m" puts "There are #{8.days + 4.hours + 20.minutes + 36.seconds} seconds in 8 days 4 hours 20 minutes 36 seconds" puts "" m = MethodGeneratorTest.new m.hello 'world' puts "" puts "add(10, 20) == #{m.add(10, 20)}" begin puts %{add("Hello", "world") == #{m.add("Hello", "world")}} rescue puts "ERROR: #{$!}" end begin puts %{add(1) == #{m.add(1)}} rescue puts "ERROR: #{$!}" end The output of that code: 10cm + 28mm + 2dm + 4m = 4.328m There are 706836 seconds in 8 days 4 hours 20 minutes 36 seconds [DEBUG] Sun Sep 06 20:46:32 -0700 2009: Entering method MethodGeneratorTest::hello. Params: (world) Hello, world! [DEBUG] Sun Sep 06 20:46:32 -0700 2009: Exiting method MethodGeneratorTest::hello. Time elapsed: 0.000118 seconds add(10, 20) == 30 ERROR: Excepting Fixnum for paramter 0. Got String. ERROR: Not enough number of arguments. Excepting 2. Got 1 |
[1]
Forum Information |
||||||||||
|