Shinyshell Community Forums > Coding >
Ruby Metaprogramming


[1]


September 07 02009, 03:37 GMT
SpaceMan
Member

SpaceMan's avatar
Location: Earth
Post count: 32
ca
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
 
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
  Currently Active Members [detailed] (0 members and ? guests)
-
Forum Statistics
Topics: 0, Posts: 0, Members: 108.
Welcome to our newest member, adamthephantump
Most members online was 5, on August 28 2009, at 21:49:28.
Legend
    Forums with unread topics in them are indicated by a strong yellow colouring around the forum icon.
    Forums with no unread topics have the standard pale yellow colouring around the forum icon.
    Forums with a blue arrow icon will redirect you to a non-forum page.
    Locked forums have a little padlock by their icon. You won't be able to post in these forums.
Shinyshell Home | Contact | Staff List | Archive | Top 

Conventional Login

Don't have an account? You may want to create one.

OpenID Login
OpenID login and registration is usable, but not finished.
What is OpenID?
Search

(advanced search)
Site Stats
  Total members: 108
  Latest member: adamthephantump
  Members currently online: 0
  Most online: 5 - Aug 28, 2009 (21:49)
  Front page hits: 88005
Developer info
  Site version: 3.5 Alpha
  16 queries - 9 templates
Under the Spotlight
Collide Site
Collide make fabulously dreamy electronic-industrial music, they're one of my favourite bands! Give them a chance to take control of your life - myspace | youtube - "Euphoria".

Collide Site - Hits: 4597

5/5 (2) | Rate this site?