Download presentation
Presentation is loading. Please wait.
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 Hierarchy Initialization
class Employee = 1 def initialize(first,last,dept,boss) @fname = first.to_s @lname = last.to_s @deptid = dept @supervisor = boss @empid = = + 1 end
9
Employee Class Hierarchy Writer Methods
def deptid=(dept) # deptid = dept @deptid = dept end def supervisor=(boss) @supervisor = boss
10
Employee Class Hierarchy Reader Methods
def name # not an attribute @lname + ", " end def empid; @empid; end def end def supervisor @supervisor
11
Employee Class Hierarchy String Conversion Reader
def to_s @empid.to_s + " : " + name + " : " + " (" + @supervisor.to_s + ")" end end # Employee
12
Employee Class Hierarchy Alternate Initialization
class Employee = 1 attr_accessor :deptid, :supervisor attr_reader :empid def initialize(first,last,dept,boss) # as before end
13
Employee Class Hierarchy Other Reader Methods
def name @lname + ", " end def to_s @empid.to_s + " : " + name + " : " + " (" + @supervisor.to_s + ")" end # Employee
14
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 + ", " end # Staff
15
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", puts "s1.class ==> " puts "s1.to_s ==> " puts "s2.to_s ==> " @s1.deptid = "Chancellor" puts "s1.to_s ==> " puts "s1.methods ==> " + @s1.methods.join(", ") end end # TestEmployee
16
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
17
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”
18
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 using context of the object enabling statement execution and state changes
19
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
20
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
21
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 class Chair < Faculty OUTPUTS New subclass: Faculty New subclass: Chair
22
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
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 Example Input Data File
# SVCLFOWLER MS SVCLHOHPE DX SVCLTWO x10301MRP USGE10301TWO x
25
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
26
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"/> </ReaderConfiguration>
27
Martin Fowler DSL Example Ruby Data Description
mapping('SVCL', ServiceCall) do extract 4..18, 'customer_name' extract , 'customer_ID' extract , 'call_type_code' extract , 'date_of_call_string' end mapping('USGE', Usage) do extract 9..22, 'customer_name' extract 4..8, 'customer_ID' extract , 'cycle' extract , 'read_date‘
28
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 = rb_file.close
29
Martin Fowler DSL Example Ruby DSL Class (2 of 3)
def mapping(code,target) @cur_mapping = ReaderFramework::ReaderStrategy.new( code,target) 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#BuilderRubyDSL
30
Martin Fowler DSL Example Ruby 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
31
Using Blocks and Iterators Inverted Index (1)
class InvertedIndex = /(\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 = true} else @stops = DEFAULT_STOPS end
32
Using Blocks and Iterators Inverted Index (2)
def index_file(filename) == nil STDERR.puts("#{filename} already indexed.") return end unless File.exist? Filename STDERR.puts("#{filename} does not exist.") unless File.readable? Filename STDERR. puts("#{filename} is not readable.") @files_indexed << filename
33
Using Blocks and Iterators Inverted Index (3)
inf = File.new(filename) lineno = 0 inf.each do |s| lineno += 1 words = {|a| a[0].downcase} words = words.reject words = words.map {|w| [w,[filename,[lineno]]]} words.each do |p| @index[p[0]] = [] unless @index.has_key? p[0] @index[p[0]] 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] acc block self end#index_file
35
Using Blocks and Iterators Inverted Index (5)
def lookup(word) @index[word].map {|f| [f[0].clone, f[1].clone] } else nil end # …
36
Questions
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.