1 / 36

Reflexive Metaprogramming in Ruby

Reflexive Metaprogramming in Ruby. H. Conrad Cunningham Computer and Information Science University of Mississippi. Metaprogramming. Metaprogramming : writing programs that write or manipulate programs as data Reflexive metaprogramming: writing programs that manipulate themselves as data.

dyllis
Download Presentation

Reflexive Metaprogramming in Ruby

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Reflexive Metaprogramming in Ruby H. Conrad Cunningham Computer and Information Science University of Mississippi

  2. Metaprogramming Metaprogramming: writing programs that write or manipulate programs as data Reflexive metaprogramming: writing programs that manipulate themselves as data

  3. Reflexive Metaprogramming Languages • Early • Lisp • Smalltalk • More recent • Ruby • Python • Groovy

  4. Basic Characteristics of Ruby(1 of 2) • Interpreted • Purely object-oriented • Single inheritance with mixins • Garbage collected • Dynamically, but strongly typed • “Duck typed” • Message passing (to methods)

  5. Basic Characteristics of Ruby (2 of 2) • Flexible syntax • optional parentheses on method calls • variable number of arguments • two block syntax alternatives • symbol data type • String manipulation facilities • regular expressions • string interpolation • Array and hash data structures

  6. Why Ruby Supportive of Reflexive Metaprogramming (1 of 2) • Open classes • Executable declarations • Dynamic method definition, removal, hiding, and aliasing • Runtime callbacks for • program changes (e.g. method_added) • missing methods (missing_method)

  7. Why Ruby Supportive of Reflexive Metaprogramming (2 of 2) • Dynamic evaluation of strings as code • at module level for declarations (class_eval) • at object level for computation (instance_eval) • Reflection (e.g. kind_of?, methods) • Singleton classes/methods for objects • Mixin modules (e.g. Enumerable) • Blocks and closures • Continuations

  8. Employee Class HierarchyInitialization class Employee @@nextid = 1 def initialize(first,last,dept,boss) @fname = first.to_s @lname = last.to_s @deptid = dept @supervisor = boss @empid = @@nextid @@nextid = @@nextid + 1 end

  9. Employee Class HierarchyWriter Methods def deptid=(dept) # deptid = dept @deptid = dept end def supervisor=(boss) @supervisor = boss end

  10. Employee Class HierarchyReader Methods def name # not an attribute @lname + ", " + @fname end def empid; @empid; end def deptid; @deptid; end def supervisor @supervisor end

  11. Employee Class HierarchyString Conversion Reader def to_s @empid.to_s + " : " + name + " : " + @deptid.to_s + " (" + @supervisor.to_s + ")" end end # Employee

  12. Employee Class HierarchyAlternate Initialization class Employee @@nextid = 1 attr_accessor :deptid, :supervisor attr_reader :empid def initialize(first,last,dept,boss) # as before end

  13. Employee Class HierarchyOtherReader Methods def name @lname + ", " + @fname end def to_s @empid.to_s + " : " + name + " : " + @deptid.to_s + " (" + @supervisor.to_s + ")" end end # Employee

  14. Employee Class HierarchyStaff Subclass class Staff < Employee attr_accessor :title def initialize(first,last,dept, boss,title) super(first,last,dept,boss) @title = title end def to_s super.to_s + ", " + @title.to_s end end # Staff

  15. Employee Class HierarchyUsing Employee Classes class TestEmployee def TestEmployee.do_test @s1 = Staff.new("Robert", "Khayat", "Law", nil, "Chancellor") @s2 = Staff.new("Carolyn", "Staton", "Law", @s1,"Provost") puts "s1.class ==> " + @s1.class.to_s puts "s1.to_s ==> " + @s1.to_s puts "s2.to_s ==> " + @s2.to_s @s1.deptid = "Chancellor" puts "s1.to_s ==> " + @s1.to_s puts "s1.methods ==> " + @s1.methods.join(", ") end end # TestEmployee

  16. Employee Class HierarchyTestEmployee.do_test Output irb irb(main):001:0> load "Employee.rb" => true irb(main):002:0> TestEmployee.do_test s1.class ==> Staff s1.to_s ==> 1 : Khayat, Robert : Law (), Chancellor s2.to_s ==> 2 : Staton, Carolyn : Law (1 : Khayat, Robert : Law (), Chancellor), Provost s1.to_s ==> 1 : Khayat, Robert : Chancellor (), Chancellor s1.methods ==> to_a, respond_to?, display, deptid, type, protected_methods, require, deptid=, title, … kind_of? => nil

  17. Ruby MetaprogrammingClass Macros • Every class has Class object where instance methods reside • Class definition is executable • Class Class extends class Module • Instance methods of class Module available during definition of classes • Result is essentially “class macros”

  18. Ruby MetaprogrammingCodeString Evaluation class_eval instance method ofclass Module • evaluates string as Ruby code • using context of class Module • enabling definition of new methods and constants instance_eval instance method of class Object • evaluates string as Ruby code • using context of the object • enabling statement execution and state changes

  19. Ruby MetaprogrammingImplementing attr_reader # Not really implemented this way class Module # add to system class def attr_reader(*syms) syms.each do |sym| class_eval%{def #{sym} @#{sym} end} end # syms.each end # attr_reader end # Module

  20. Ruby MetaprogrammingImplementing attr_writer # Not really implemented this way class Module # add to system class def attr_writer(*syms) syms.each do |sym| class_eval %{def #{sym}=(val) @#{sym} = val end} end end # attr_writer end # Module

  21. Ruby MetaprogrammingRuntime Callbacks class Employee # class definitions executable def Employee.inherited(sub) # class method puts "New subclass: #{sub}" # of Class end class Faculty < Employee end class Chair < Faculty end OUTPUTS New subclass: Faculty New subclass: Chair

  22. Ruby MetaprogrammingRuntime Callbacks class Employee def method_missing(meth,*args) # instance method mstr = meth.to_s # of Object last = mstr[-1,1] base = mstr[0..-2] if last == "=" class_eval("attr_writer :#{base}") else class_eval("attr_reader :#{mstr}") end end end

  23. Domain Specific Languages (DSL) • Programming or description language designed for particular family of problems • Specialized syntax and semantics • Alternative approaches • External language with specialized interpreter • Internal (embedded) language by tailoring a general purpose language

  24. Martin Fowler DSL ExampleInput Data File #123456789012345678901234567890123456 SVCLFOWLER 10101MS0120050313 SVCLHOHPE 10201DX0320050315 SVCLTWO x10301MRP220050329 USGE10301TWO x50214..7050329

  25. Martin Fowler DSL ExampleText Data Description mapping SVCL dsl.ServiceCall 4-18: CustomerName 19-23: CustomerID 24-27 : CallTypeCode 28-35 : DateOfCallString mapping USGE dsl.Usage 4-8 : CustomerID 9-22: CustomerName 30-30: Cycle 31-36: ReadDate

  26. Martin Fowler DSL ExampleXML Data Description <ReaderConfiguration> <Mapping Code = "SVCL" TargetClass = "dsl.ServiceCall"> <Field name = "CustomerName" start = "4" end = "18"/> <Field name = "CustomerID" start = "19" end = "23"/> <Field name = "CallTypeCode" start = "24" end = "27"/> <Field name = "DateOfCallString" start = "28" end = "35"/> </Mapping> <Mapping Code = "USGE" TargetClass = "dsl.Usage"> <Field name = "CustomerID" start = "4" end = "8"/> <Field name = "CustomerName" start = "9" end = "22"/> <Field name = "Cycle" start = "30" end = "30"/> <Field name = "ReadDate" start = "31" end = "36"/> </Mapping> </ReaderConfiguration>

  27. Martin Fowler DSL ExampleRuby Data Description mapping('SVCL', ServiceCall) do extract 4..18, 'customer_name' extract 19..23, 'customer_ID' extract 24..27, 'call_type_code' extract 28..35, 'date_of_call_string' end mapping('USGE', Usage) do extract 9..22, 'customer_name' extract 4..8, 'customer_ID' extract 30..30, 'cycle' extract 31..36, 'read_date‘ end

  28. Martin Fowler DSL ExampleRuby DSL Class (1) require 'ReaderFramework' class BuilderRubyDSL def initialize(filename) @rb_dsl_file = filename end def configure(reader) @reader = reader rb_file = File.new(@rb_dsl_file) instance_eval(rb_file.read, @rb_dsl_file) rb_file.close end

  29. Martin Fowler DSL ExampleRuby DSL Class (2 of 3) def mapping(code,target) @cur_mapping = ReaderFramework::ReaderStrategy.new( code,target) @reader.add_strategy(@cur_mapping) yield end def extract(range,field_name) begin_col = range.begin end_col = range.end end_col -= 1 if range.exclude_end? @cur_mapping.add_field_extractor( begin_col,end_col,field_name) end end#BuilderRubyDSL

  30. Martin Fowler DSL ExampleRuby DSL Class (3 of 3) class ServiceCall; end class Usage; end class TestRubyDSL def TestRubyDSL.run rdr = ReaderFramework::Reader.new cfg = BuilderRubyDSL.new("dslinput.rb") cfg.configure(rdr) inp = File.new("fowlerdata.txt") res = rdr.process(inp) inp.close res.each {|o| puts o.inspect} end end

  31. Using Blocks and Iterators Inverted Index (1) class InvertedIndex @@wp = /(\w+([-'.]\w+)*)/ DEFAULT_STOPS = {"the" => true, "a" => true, "an" => true} def initialize(*args) @files_indexed = [] @index = Hash.new @stops = Hash.new if args.size == 1 args[0].each {|w| @stops[w] = true} else @stops = DEFAULT_STOPS end end

  32. Using Blocks and Iterators Inverted Index (2) def index_file(filename) unless @files_indexed.index(filename) == nil STDERR.puts("#{filename} already indexed.") return end unless File.exist? Filename STDERR.puts("#{filename} does not exist.") return end unless File.readable? Filename STDERR. puts("#{filename} is not readable.") return end @files_indexed << filename

  33. Using Blocks and Iterators Inverted Index (3) inf = File.new(filename) lineno = 0 inf.each do |s| lineno += 1 words = s.scan(@@wp).map {|a| a[0].downcase} words = words.reject {|w| @stops[w]} words = words.map {|w| [w,[filename,[lineno]]]} words.each do |p| @index[p[0]] = [] unless @index.has_key? p[0] @index[p[0]] = @index[p[0]].push(p[1]) end end inf.close

  34. Using Blocks and Iterators Inverted Index (4) @index.each do |k,v| # k => v is hash entry @index[k] = v.sort {|a,b| a[0] <=> b[0]} end @index.each do |k,v| @index[k] = v.slice(1...v.length).inject([v[0]]) do |acc, e| if acc[-1][0] == e[0] acc[-1][1] = acc[-1][1] + e[1] else acc = acc + [e] end acc end end#@index.each's block self end#index_file

  35. Using Blocks and Iterators Inverted Index (5) def lookup(word) if @index[word] @index[word].map {|f| [f[0].clone, f[1].clone] } else nil end end # … end

  36. Questions

More Related