metaprogramming+
domainspecific
languages
% cat ddfreyne.txt
DENIS DEFREYNE==============
web: http://stoneship.orgtwitter: ddfreyne
nanoc: a static ruby web site publishing systemhttp://nanoc.stoneship.org/
% _
what is metaprogramming?
Metaprogramming is the writing of computer
programs that write or manipulate other
programs (or themselves) as their data.
class Person
attr_reader :friends
end
class Document < ActiveRecord::Base
has_many :pages
end
task :test do puts "Hello, I am a rake task!"end
example (i)
class Bob my_attr_reader :foo, :barend
class Module
def my_attr_reader(*fields) fields.each do |field|
class_eval " def #{field} @#{field} end "
end end
end
class Module
def my_attr_reader(*fields) fields.each do |field|
class_eval " def #{field} @#{field} end "
end end
end
def #{field} @#{field}end
def foo @fooend
def bar @barend
class Module
def my_attr_reader(*fields) fields.each do |field|
class_eval " def #{field} @#{field} end "
end end
end
DEMO
example (ii)
class Module
def my_attr_reader(*fields) fields.each do |field|
class_eval " def #{field} @#{field} end "
end end
end
class Module
def battr_reader(*fields) fields.each do |field|
class_eval " def #{field}? !!@#{field} end "
end end
end
class Bob battr_reader :foo, :barend
p bob.foo?p bob.bar?
DEMO
example (iii)
<p>Hello. My name is <%= @first_name %>.</p>
template = "<p>Hello. My name is <%= @first_name %>.</p>"
@first_name = "Bob"
p ERB.new(template).result
DEMO
example (iv)
class Product def initialize @title = "My First Ruby Book" @id = "39t8zfeg" endend
template = "<p>Product <%= @id %> has title <%= @title %>.</p>"
product = Product.new
# TODO use the product details p ERB.new(template).result
class Product def initialize @title = "My First Ruby Book" @id = "39t8zfeg" end
def print template = "<p>Product <%= @id %> has title <%= @title %>.</p>"
puts ERB.new(template).result(binding) endend
class Product def initialize @title = "My First Ruby Book" @id = "39t8zfeg" end
def print template = "<p>Product <%= @id %> has title <%= @title %>.</p>"
puts ERB.new(template).result(binding) endend
class Product
def initialize @title = "My First Ruby Book" @id = "39t8zfeg" end
end
class Product
def initialize @title = "My First Ruby Book" @id = "39t8zfeg" end
def get_binding binding end
end
def get_binding bindingend
def get_binding bindingend
def binding bindingend
def binding bindingend
SystemStackError: stack level too deep
template = "<p>Product <%= @id %> has title <%= @title %>.</p>"
product = Product.new
p ERB.new(template).result
template = "<p>Product <%= @id %> has title <%= @title %>.</p>"
product = Product.new
p ERB.new(template).result(product.get_binding)
DEMO
example (v)
compile '/articles/*/' do filter :erb filter :bluecloth
layout 'article'
filter :rubypantsend
my-‐site/ config.yaml Rules content/ layouts/ lib/
process /oo/ do |item| puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
… simpler
class Item
attr_reader :identifier
def initialize(identifier) @identifier = identifier end
end
items = [ Item.new('foo'), Item.new('foobar'), Item.new('quxbar'), Item.new('moo')]
magically_load_rules
items.each do |item| magically_process(item)end
class Rule
def initialize(pattern, block) @pattern = pattern @block = block end
def applicable_to?(item) item.identifier =~ @pattern end
def apply_to(item) @block.call(item) end
end
class Application
def initialize @rules = [] end
def load_rules rules_content = File.read('Rules') dsl = DSL.new(@rules) dsl.instance_eval(rules_content) end
⋮
class Application
def initialize @rules = [] end
def load_rules rules_content = File.read('Rules') dsl = DSL.new(@rules) dsl.instance_eval(rules_content) end
⋮
class Bob
def initialize @secret = "abc" end
end
bob = Bob.newp bob.secret
NoMethodError: undefined method `secret' for #<Bob:0x574324>
bob = Bob.newp bob.instance_eval { @secret }
abc
bob = Bob.newp bob.instance_eval "@secret"
abc
class Application
def initialize @rules = [] end
def load_rules rules_content = File.read('Rules') dsl = DSL.new(@rules) dsl.instance_eval(rules_content) end
⋮
⋮
def process(item) rule = rules.find do |r| r.applicable_to?(item) end
rule.apply_to(item) end
end
class DSL
def initialize(rules) @rules = rules end
def process(pattern, &block) @rules << Rule.new(pattern, block) end
end
class DSL
def initialize(rules) @rules = rules end
def process(pattern, &block) @rules << Rule.new(pattern, block) end
end
process /oo/ do |item| puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
process /oo/ do |item| puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
items = [ Item.new('foo'), Item.new('foobar'), Item.new('quxbar'), Item.new('moo')]
app = App.newapp.load_rules
items.each do |item| app.process(item)end
DEMO
example (vi)
process /oo/ do |item| puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
process /oo/ do puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
class RuleContext
def initialize(item) @item = item end
end
class Rule
def initialize(pattern, block) @pattern = pattern @block = block end
def applicable_to?(item) item.identifier =~ @pattern end
def apply_to(item) # original way: @block.call(item) end
end
class Rule
def initialize(pattern, block) @pattern = pattern @block = block end
def applicable_to?(item) item.identifier =~ @pattern end
def apply_to(item) rule_context = RuleContext.new(item) rule_context.instance_eval(&@block) end
end
process /oo/ do puts "I am rule /oo/!" puts "I am processing #{@item.inspect}!"end
process /oo/ do puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
class RuleContext
def initialize(item) @item = item end
end
class RuleContext
def initialize(item) @item = item end
def item @item end
end
process /oo/ do puts "I am rule /oo/!" puts "I am processing #{item.inspect}!"end
DEMO
‣ The Ruby Object Modeland Metaprogramminghttp://www.pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming
‣ How nanocʼs Rules DSL Workshttp://stoneship.org/journal/2009/how-nanocs-rules-dsl-works/
you can hazquestions?
k thx bai