Title: Reflexive Metaprogramming in Ruby
1Reflexive Metaprogramming in Ruby
- H. Conrad Cunningham
- Computer and Information Science
- University of Mississippi
2Metaprogramming
- Metaprogramming writing programs that write or
manipulate programs as data - Reflexive metaprogramming writing programs that
manipulate themselves as data
3Reflexive Metaprogramming Languages
- Early
- Lisp
- Smalltalk
- More recent
- Ruby
- Python
- Groovy
4Basic 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)
5Basic 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
6Why 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)
7Why 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
8Employee Class HierarchyInitialization
- class Employee
- _at__at_nextid 1
- def initialize(first,last,dept,boss)
- _at_fname first.to_s
- _at_lname last.to_s
- _at_deptid dept
- _at_supervisor boss
- _at_empid _at__at_nextid
- _at__at_nextid _at__at_nextid 1
- end
9Employee Class HierarchyWriter Methods
- def deptid(dept) deptid dept
- _at_deptid dept
- end
- def supervisor(boss)
- _at_supervisor boss
- end
10Employee Class HierarchyReader Methods
-
- def name not an attribute
- _at_lname ", " _at_fname
- end
- def empid _at_empid end
- def deptid _at_deptid end
- def supervisor
- _at_supervisor
- end
11Employee Class HierarchyString Conversion Reader
- def to_s
- _at_empid.to_s " " name
- " " _at_deptid.to_s " ("
- _at_supervisor.to_s ")"
- end
- end Employee
12Employee Class HierarchyAlternate Initialization
- class Employee
- _at__at_nextid 1
- attr_accessor deptid, supervisor
- attr_reader empid
- def initialize(first,last,dept,boss)
- as before
- end
13Employee Class HierarchyOther Reader Methods
-
- def name
- _at_lname ", " _at_fname
- end
- def to_s
- _at_empid.to_s " " name
- " " _at_deptid.to_s " ("
- _at_supervisor.to_s ")"
- end
- end Employee
14Employee Class HierarchyStaff Subclass
- class Staff lt Employee
- attr_accessor title
- def initialize(first,last,dept,
- boss,title)
- super(first,last,dept,boss)
- _at_title title
- end
- def to_s
- super.to_s ", " _at_title.to_s
- end
- end Staff
15Employee Class HierarchyUsing Employee Classes
- class TestEmployee
- def TestEmployee.do_test
- _at_s1 Staff.new("Robert", "Khayat",
- "Law", nil, "Chancellor")
- _at_s2 Staff.new("Carolyn", "Staton",
- "Law", _at_s1,"Provost")
- puts "s1.class gt " _at_s1.class.to_s
- puts "s1.to_s gt " _at_s1.to_s
- puts "s2.to_s gt " _at_s2.to_s
- _at_s1.deptid "Chancellor"
- puts "s1.to_s gt " _at_s1.to_s
- puts "s1.methods gt "
- _at_s1.methods.join(", ")
- end
- end TestEmployee
16Employee Class HierarchyTestEmployee.do_test
Output
- irb
- irb(main)0010gt load "Employee.rb"
- gt true
- irb(main)0020gt TestEmployee.do_test
- s1.class gt Staff
- s1.to_s gt 1 Khayat, Robert Law (),
Chancellor - s2.to_s gt 2 Staton, Carolyn Law (1
Khayat, Robert Law (), Chancellor), Provost - s1.to_s gt 1 Khayat, Robert Chancellor (),
Chancellor - s1.methods gt to_a, respond_to?, display,
deptid, type, protected_methods, require,
deptid, title, kind_of? - gt nil
17Ruby 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
18Ruby MetaprogrammingCode 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
19Ruby 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
- _at_sym
- end
- end syms.each
- end attr_reader
- end Module
20Ruby 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)
- _at_sym val
- end
- end
- end attr_writer
- end Module
21Ruby MetaprogrammingRuntime Callbacks
- class Employee class definitions executable
- def Employee.inherited(sub) class method
- puts "New subclass sub" of Class
- end
- class Faculty lt Employee
- end
- class Chair lt Faculty
- end
- OUTPUTS
- New subclass Faculty
- New subclass Chair
22Ruby MetaprogrammingRuntime Callbacks
- class Employee
- def method_missing(meth,args) instance
method - mstr meth.to_s of Object
- last mstr-1,1
- base mstr0..-2
- if last ""
- class_eval("attr_writer base")
- else
- class_eval("attr_reader mstr")
- end
- end
- end
23Domain 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
24Martin Fowler DSL ExampleInput Data File
- 123456789012345678901234567890123456
- SVCLFOWLER 10101MS0120050313
- SVCLHOHPE 10201DX0320050315
- SVCLTWO x10301MRP220050329
- USGE10301TWO x50214..7050329
25Martin 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
26Martin Fowler DSL ExampleXML Data Description
- ltReaderConfigurationgt
- ltMapping Code "SVCL" TargetClass
"dsl.ServiceCall"gt - ltField name "CustomerName" start "4"
- end "18"/gt
- ltField name "CustomerID" start "19" end
"23"/gt - ltField name "CallTypeCode" start "24"
- end "27"/gt
- ltField name "DateOfCallString" start "28"
- end "35"/gt
- lt/Mappinggt
- ltMapping Code "USGE" TargetClass
"dsl.Usage"gt - ltField name "CustomerID" start "4" end
"8"/gt - ltField name "CustomerName" start "9"
- end "22"/gt
- ltField name "Cycle" start "30" end
"30"/gt - ltField name "ReadDate" start "31" end
"36"/gt - lt/Mappinggt
- lt/ReaderConfigurationgt
27Martin 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
28Martin Fowler DSL ExampleRuby DSL Class (1)
- require 'ReaderFramework'
- class BuilderRubyDSL
- def initialize(filename)
- _at_rb_dsl_file filename
- end
- def configure(reader)
- _at_reader reader
- rb_file File.new(_at_rb_dsl_file)
- instance_eval(rb_file.read, _at_rb_dsl_file)
- rb_file.close
- end
-
29Martin Fowler DSL ExampleRuby DSL Class (2 of 3)
- def mapping(code,target)
- _at_cur_mapping ReaderFrameworkReaderStrategy
.new( - code,target)
- _at_reader.add_strategy(_at_cur_mapping)
- yield
- end
- def extract(range,field_name)
- begin_col range.begin
- end_col range.end
- end_col - 1 if range.exclude_end?
- _at_cur_mapping.add_field_extractor(
- begin_col,end_col,field_name)
- end
- endBuilderRubyDSL
30Martin Fowler DSL ExampleRuby DSL Class (3 of 3)
- class ServiceCall end
- class Usage end
- class TestRubyDSL
- def TestRubyDSL.run
- rdr ReaderFrameworkReader.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
31Using Blocks and Iterators Inverted Index (1)
- class InvertedIndex
- _at__at_wp /(\w(-'.\w))/
- DEFAULT_STOPS "the" gt true, "a" gt true,
- "an" gt true
- def initialize(args)
- _at_files_indexed
- _at_index Hash.new
- _at_stops Hash.new
- if args.size 1
- args0.each w _at_stopsw true
- else
- _at_stops DEFAULT_STOPS
- end
- end
32Using Blocks and Iterators Inverted Index (2)
- def index_file(filename)
- unless _at_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
- _at_files_indexed ltlt filename
33Using Blocks and Iterators Inverted Index (3)
- inf File.new(filename)
- lineno 0
- inf.each do s
- lineno 1
- words s.scan(_at__at_wp).map a
a0.downcase - words words.reject w _at_stopsw
- words words.map w
- w,filename,lineno
- words.each do p
- _at_indexp0 unless
- _at_index.has_key? p0
- _at_indexp0 _at_indexp0.push(p1)
- end
- end
- inf.close
34Using Blocks and Iterators Inverted Index (4)
- _at_index.each do k,v k gt v is hash entry
- _at_indexk v.sort a,b a0 ltgt b0
- end
- _at_index.each do k,v
- _at_indexk
- v.slice(1...v.length).inject(v0)
- do acc, e
- if acc-10 e0
- acc-11 acc-11 e1
- else
- acc acc e
- end
- acc
- end
- end_at_index.each's block
- self
- endindex_file
35Using Blocks and Iterators Inverted Index (5)
- def lookup(word)
- if _at_indexword
- _at_indexword.map f
- f0.clone, f1.clone
- else
- nil
- end
- end
-
- end
36Questions