metaprogramming and dsls

74
metaprogramming + domain specific languages

Upload: denis-defreyne

Post on 13-Jul-2015

590 views

Category:

Technology


0 download

TRANSCRIPT

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