Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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 2 Reflexive Metaprogramming Languages Early – – Lisp Smalltalk More recent – – – Ruby Python Groovy 3 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) 4 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 5 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) 6 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 7 Employee Class Hierarchy Initialization 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 8 Employee Class Hierarchy Writer Methods def deptid=(dept) # deptid = dept @deptid = dept end def supervisor=(boss) @supervisor = boss end 9 Employee Class Hierarchy Reader Methods def name # not an attribute @lname + ", " + @fname end def empid; @empid; end def deptid; @deptid; end def supervisor @supervisor end 10 Employee Class Hierarchy String Conversion Reader def to_s @empid.to_s + " : " + name + " : " + @deptid.to_s + " (" + @supervisor.to_s + ")" end end # Employee 11 Employee Class Hierarchy Alternate Initialization class Employee @@nextid = 1 attr_accessor :deptid, :supervisor attr_reader :empid def initialize(first,last,dept,boss) # as before end 12 Employee Class Hierarchy Other Reader Methods def name @lname + ", " + @fname end def to_s @empid.to_s + " : " + name + " : " + @deptid.to_s + " (" + @supervisor.to_s + ")" end end # Employee 13 Employee Class Hierarchy Staff 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 14 Employee Class Hierarchy Using 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 15 Employee Class Hierarchy TestEmployee.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 16 Ruby Metaprogramming Class 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” 17 Ruby Metaprogramming Code String Evaluation class_eval instance method of class 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 18 Ruby Metaprogramming Implementing 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 19 Ruby Metaprogramming Implementing 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 20 Ruby Metaprogramming Runtime 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: New subclass: Faculty Chair 21 Ruby Metaprogramming Runtime 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 22 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 23 Martin Fowler DSL Example Input Data File #123456789012345678901234567890123456 SVCLFOWLER 10101MS0120050313 SVCLHOHPE 10201DX0320050315 SVCLTWO x10301MRP220050329 USGE10301TWO x50214..7050329 24 Martin Fowler DSL Example Text 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 25 Martin Fowler DSL Example XML 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> 26 Martin Fowler DSL Example Ruby 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 27 Martin Fowler DSL Example Ruby 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 28 Martin Fowler DSL Example Ruby 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 29 Martin Fowler DSL Example Ruby DSL Class (3 of 3) class ServiceCall; class Usage; end 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 30 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 31 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 32 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 33 inf.close 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 34 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 35 Questions 36